How to test .then(function in Unit Testing AgularJS with jasmine - angularjs

I am not clear how to use SpyOn in Unit Testing...
I have the following controller
(function () {
'use strict';
angular.module('otpConfigureDatasets').controller('otpActivityCardController', otpActivityCardController);
otpActivityCardController.$inject = ['$location', '$state', 'otpWebMapApp', 'otpWMDeltaTracker', 'otpWMStateCache', '$scope', '$timeout', 'otpActivityCardService', 'otpControlCenterData'];
function otpActivityCardController($location, $state, otpWebMapApp, otpWMDeltaTracker, otpWMStateCache, $scope, $timeout, otpActivityCardService, otpControlCenterData) {
var vm = this;
vm.cards = [];
otpActivityCardService.getActivityCards().then(function (resolve) {
vm.cards = resolve;
});
//.....Some code ....
})();
I need to test the GetActivityCards().then(function ...
I tried test it using the code below
'use strict';
describe('Test controller (activityCard) in Page MyDatasets', function() {
var MainCtrl, $state, scope, otpWebMapApp, otpWMDeltaTracker, otpWMStateCache, otpActivityCardService, otpControlCenterData;
var card;
beforeEach(function() {
module('otpConfigureDatasets');
});
beforeEach(inject(function ($controller, $rootScope, _$state_, _otpWebMapApp_, _otpWMDeltaTracker_, _otpWMStateCache_, _otpActivityCardService_, _otpControlCenterData_) {
scope = $rootScope.$new();
scope.$parent = { $parent: { menuParentGroupClick: function menuParentGroupClick() { } } };
MainCtrl = $controller('otpActivityCardController', {
$scope: scope
});
otpWebMapApp = _otpWebMapApp_;
otpWMDeltaTracker = _otpWMDeltaTracker_;
otpWMStateCache = _otpWMStateCache_;
otpActivityCardService = _otpActivityCardService_;
otpControlCenterData = otpControlCenterData;
}));
it('Test Function', function() {
spyOn(otpActivityCardService, 'getActivityCards');
expect(otpActivityCardService.getActivityCards).toHaveBeenCalled();
});
});
But I am getting this error:
Expected spy getActivityCards to have been called.
Error: Expected spy getActivityCards to have been called.
What is wrong?

You created a spy to the "getActivityCards" function, but you didn't call it in your test (unless you hid this line of code from the example).
When you create a Jasmine Spy to a function, you are only "watching" this function, you can check if it was called, you can mock the return values of it, you can check the parameters of a call to it, i.e, you can check a lot of things about the call history of the function, but you still need to explicity make a call to the function (or to a function in your controller that calls the spied function from it).
So you are spying the Service, and you are testing the Controller, your test should look something like:
it('Test Function', function() {
spyOn(otpActivityCardService, 'getActivityCards');
otpActivityCardService.getActivityCards();
expect(otpActivityCardService.getActivityCards).toHaveBeenCalled();
});
On a side note, to be more testable, your controller should encapsulate your service call in a function in your controller, like:
(function () {
'use strict';
angular.module('otpConfigureDatasets').controller('otpActivityCardController', otpActivityCardController);
otpActivityCardController.$inject = ['$location', '$state', 'otpWebMapApp', 'otpWMDeltaTracker', 'otpWMStateCache', '$scope', '$timeout', 'otpActivityCardService', 'otpControlCenterData'];
function otpActivityCardController($location, $state, otpWebMapApp, otpWMDeltaTracker, otpWMStateCache, $scope, $timeout, otpActivityCardService, otpControlCenterData) {
var vm = this;
vm.cards = [];
vm.getCards = function () {
otpActivityCardService.getActivityCards().then(function (resolve) {
vm.cards = resolve;
});
}
vm.getCards();
//.....Some code ....
})();
So you could create a test that really tested a function in your controller (because the way you are describing your test case, it really should be a Service test only)
it('Better test case', function() {
spyOn(otpActivityCardService, 'getActivityCards');
MainCtrl.getCards();
expect(otpActivityCardService.getActivityCards).toHaveBeenCalled();
});

Related

Angular test cases mocking controller as value

I am stuck in writing test cases of angular directive. I am injecting my controller in test and i am able to stub $scope value but i am not able to stub this value of controller, which is giving me undefined and test case not passing. I have following angular controller:
export default class myCtrl {
constructor($rootScope, $ngRedux, $scope) {
const unsubscribe = $ngRedux.connect(this.mapStateToThis, bodyActions)(this);
$scope.$on('$destroy', unsubscribe);
const step = this;
step.buttonClassName = 'sBtn';
step.appState.selectedService = step.appState.selectedService || '';
console.log(step.customFunction());
}
customFunction() {
return 'test';
}
mapStateToThis(state) {
return {
appState: state.app
};
}
}
This is following test case code:
describe('directive test case', function () {
var $scope,
$controller,
compile,
directiveElem,
innerScope,
element,
compiledElement,
state, step;
beforeEach(inject(function ($compile, $rootScope, $controller, $state, _$httpBackend_, $q) { inject arguments of given function
$httpBackend = _$httpBackend_
state = $state
compile = $compile
$scope = $rootScope.$new();
$scope.test = {};
$httpBackend.when('GET', 'languages/locale-en.json').respond(languageEng);
console.log(myCtrl);
step = $controller(myCtrl, {$scope: $scope, test: 'test'}); // Problem is here
}
}
As you can see i am using controller as syntax. So step having this value. I am not able to set default test value for step.appState or mock it.
How can I stub/mock step's functions and variables from test?
Any suggestions?

How to invoke spyOn on a scope function

I have the following jasmine spec.
describe('ViewMeetingCtrl', function () {
var $rootScope, scope, $controller , $q ;
beforeEach(angular.mock.module('MyApp'));
beforeEach(inject(function ($rootScope, $controller ) {
scope = $rootScope.$new();
createController = function() {
return $controller('ViewMeetingCtrl', {
$scope: scope,
meeting : {}
});
};
}));
it('the meeting type should be equal to an object', function () {
var controller = new createController();
//some assertion
});
});
Following is my ViewMeetingCtrl.js
(function () {
'use strict';
angular.module('MyApp').controller('ViewMeetingCtrl', ViewMeetingCtrl);
ViewMeetingCtrl.$inject = ['$scope', '$state', '$http', '$translate', 'notificationService', 'meetingService', '$modal', 'meeting', 'attachmentService'];
function ViewMeetingCtrl($scope, $state, $http, $translate, notificationService, meetingService, $modal, meeting, attachmentService) {
$scope.meeting = meeting;
$scope.cancelMeeting = cancelMeeting;
function cancelMeeting(meetingId, companyId) {
meetingService.sendCancelNotices(companyId, meetingId)
.success(function () {
$state.go('company.view');
});
}
//more code
}
})();
My question that how do i invoke the spyOn (or any other jasmine spies related method) method on the above cancelMeeting() so that i can mock the method calls , returns etc. I did the following
describe('ViewMeetingCtrl', function () {
var $rootScope, scope, $controller , $q ;
beforeEach(angular.mock.module('MyApp'));
beforeEach(inject(function ($rootScope, $controller ) {
scope = $rootScope.$new();
createController = function() {
return $controller('ViewMeetingCtrl', {
$scope: scope,
meeting : {}
});
};
}));
it('the meeting type should be equal to an object', function () {
spyOn(scope, 'cancelMeeting');//cancelMeeting is inside the scope so did like this
var controller = new createController();
});
});
but i get the following output
Firefox 37.0.0 (Windows 8.1) ViewMeetingCtrl the meeting type should be equal to an object FAILED
Error: cancelMeeting() method does not exist in C:/Users/Work/MyApp/Tests/node_mo
dules/jasmine-core/lib/jasmine-core/jasmine.js (line 1895)
is the way i am invoking spyOn is wrong or any another syntax's am i missing ?. Or do i missing something fundamental here ?
The cancelMeeting function is not added to the scope until the controller is created. So I think you just need to reverse the lines in your test code:
it('the meeting type should be equal to an object', function () {
var controller = new createController();
spyOn(scope, 'cancelMeeting');
});
Your test code looks good. I think you just have to switch the order of your assignments. First define cancelMeeting and than assign it.
function cancelMeeting(meetingId, companyId) {
meetingService.sendCancelNotices(companyId, meetingId)
.success(function () {
$state.go('company.view');
});
}
$scope.cancelMeeting = cancelMeeting;
Or just:
$scope.cancelMeeting = function(meetingId, companyId) {
meetingService.sendCancelNotices(companyId, meetingId)
.success(function () {
$state.go('company.view');
});
}

Angularjs: mock location.path() with spyOn for a unit test

I have already read this post (and others) but I don't manage to make this simple unit test work. I'm using the version 2 of Jasmine.
My factory is very simple:
angular.module('myApp')
.factory('detectPath', function ($location, $rootScope) {
'use strict';
var locationPath = $location.path()
function getPath () {
if (locationPath === '/') {
locationPath = 'home';
} else {
locationPath = '';
}
$rootScope.path = locationPath;
}
getPath();
return locationPath;
});
And my unit test is just as simple:
'use strict';
describe('Factory: detectPath', function () {
var detectPath, $rootScope, $location;
beforeEach(module('myApp'));
beforeEach(inject(function (_detectPath_, _$rootScope_, _$location_) {
detectPath = _detectPath_;
$rootScope = _$rootScope_;
$location = _$location_;
spyOn($location, 'path').and.returnValue('/');
}));
it('should return pathName', function ($location) {
expect($rootScope.path).toBe('home');
});
});
This doesn't pass the test (I get the error expect false to be "home").
What I am doing wrong?
Is there a way to verify that spyOn has been called (only once)?
There are two main problems with your code.
First of all, your getPath() function is executed before you are setting spy. You should either set the spy in the previous beforeEach or inject your factory in the test (I went for the second solution).
The second problem (which does not influence the test yet) is that you hide your $location variable with test's function argument - you will not be able to access it as it will always be undefined. After I removed this arg, I'm able to test if spy has been called with expect(...).toHaveBeenCalled().
Here is a working code:
describe('Factory: detectPath', function () {
var detectPath, $rootScope, $location;
beforeEach(module('myApp'));
beforeEach(inject(function (_$rootScope_, _$location_) {
$rootScope = _$rootScope_;
$location = _$location_;
spyOn($location, 'path').and.returnValue('/');
}));
it('should return pathName', function () {
inject(function (detectPath) {
expect($location.path).toHaveBeenCalled();
expect($rootScope.path).toBe('home');
});
});
});
And JSFiddle (using Jasmine 1.3 but the only difference in this sample is that you call and.returnValue in Jasmine 2 and returnValue in Jasmine 1.3).

AngularJS Jasmine error 'Controller is not a function' when instantiated with arguments

I have been doing angularJS for a while now (without tests) but I want to do it properly! I have a controller defined like so
(function () {
'use strict';
angular.module('app')
.controller('CarehomeListCtrl', ['$scope', 'carehomesDataService', carehomeListCtrl]);
function carehomeListCtrl($scope, carehomesDataService) {
var vm = this;
vm.carehomeCollection = [];
vm.activate = activate;
function activate() {
vm.carehomeCollection = carehomesDataService.getAllCarehomes();
}
activate();
}
})();
and then my spec
describe("Carehomes tests", function () {
var $scopeConstructor, $controllerConstructor;
beforeEach(module('app'));
beforeEach(inject(function ($controller, $rootScope) {
$controllerConstructor = $controller;
$scopeConstructor = $rootScope;
}));
describe("CarehomeListCtrl", function () {
var ctrl, dataService, scope;
function createController() {
return $controllerConstructor('CarehomeListCtrl', {
$scope: scope,
carehomesDataService: dataService
});
}
beforeEach(inject(function ($injector) {
scope = $scopeConstructor.$new();
dataService =$injector.get('carehomesDataService') ;
}));
it("should have a carehomesCollection array", function () {
ctrl = createController();
expect(ctrl.carehomesCollection).not.toBeNull();
});
it("should have 3 items in carehomesCollection array when load is called", function () {
ctrl = createController();
expect(ctrl.carehomeCollection.length).toBe(3);
});
});
});
The problem here is that the call to instantiate my controller fails with error whenever I call it with any arguments whether an empty object {} or just $scope : scope} so I know the problem is not carehomesDataService.
Result StackTrace: Error: [ng:areq] Argument 'CarehomeListCtrl' is not
a function, got undefined
http://errors.angularjs.org/1.2.26/ng/areq?p0=CarehomeListCtrl&p1=not%20a%20function%2C%20got%20undefined
However, if I instantiate that controller like this $controllerConstructor('CarehomeListCtrl'); without arguments, it gets instantiated. I'm stumped!
carehomesDataService is a custom service I have written but it's own tests pass and it is correctly injected into the controller in the application.
Any help would be massively appreciated.
Note: I do not quite agree with defining properties on the controller as the view model instead of on $scope but I am following Jesse Liberty's pluralsight course and that's how he does it....plus injecting scope isn't quite working right now which is annoying. Thanks in advance.

Unit test AngularJS controller that inherits from a base controller via $controller

The scenario is I have a ChildCtrl controller that inherits from BaseCtrl following this inheritance pattern:
angular.module('my-module', [])
.controller('BaseCtrl', function ($scope, frobnicate) {
console.log('BaseCtrl instantiated');
$scope.foo = frobnicate();
// do a bunch of stuff
})
.controller('ChildCtrl', function ($controller, $scope) {
$controller('BaseCtrl', {
$scope: $scope,
frobnicate: function () {
return 123;
}
});
});
Assuming BaseCtrl does a bunch of stuff and is already well tested, I want to test that ChildCtrl instantiates BaseCtrl with certain arguments. My initial thought was something along these lines:
describe("ChildCtrl", function () {
var BaseCtrl;
beforeEach(module('my-module'));
beforeEach(module(function($provide) {
BaseCtrl = jasmine.createSpy();
$provide.value('BaseCtrl', BaseCtrl);
}));
it("inherits from BaseCtrl", inject(function ($controller, $rootScope) {
$controller('ChildCtrl', { $scope: $rootScope.$new() });
expect(BaseCtrl).toHaveBeenCalled();
}));
});
However when I run the test the spy is never called and the console shows "BaseCtrl instantiated", indicating that $controller is using the actual controller instead of the instance I am providing with $provide.value().
What's the best way to test this?
So it looks like $controller doesn't search for controllers by name in the $provide.value() namespace. Instead you have to use the $controllerProvider.register() method, which is only accessible from the module.config() block. Fortunately it looks like there's a hook we can use to get access to $controllerProvider on the module under test.
The updated test code looks like:
describe("ChildCtrl", function () {
var BaseCtrl;
beforeEach(module('my-module', function ($controllerProvider) {
BaseCtrl = jasmine.createSpy();
BaseCtrl.$inject = ['$scope', 'frobnicate'];
$controllerProvider.register('BaseCtrl', BaseCtrl);
}));
beforeEach(inject(function ($controller, $rootScope) {
$controller('ChildCtrl', { $scope: $rootScope.$new() });
}));
it("inherits from BaseCtrl", inject(function ($controller, $rootScope) {
expect(BaseCtrl).toHaveBeenCalled();
}));
it("passes frobnicate() function to BaseCtrl that returns 123", function () {
var args = BaseCtrl.calls.argsFor(0);
var frobnicate = args[1];
expect(frobnicate()).toEqual(123);
});
});

Resources