Unit testing controller with injected dependencies - angularjs

What is the best practice to inject the dependencies into my
controller test?
When would I use the module(function($provide){})?
How do I properly check that $state.go() was called with the right arguments?
example-controller.js
angular.module('myModule')
.controller('ExampleCtrl', ['$state', 'ExampleService', 'exampleResolve',
function($state, ExampleService, exampleResolve){
var self = this;
self.property = false;
self.resolvedProperty = exampleResolve;
self.submit = function() {
ExampleService
.exampleGet()
.$promise
.then(function(res) {
$state.go('anotherView', { prop1: 'yay', prop2: 'again' });
})
};
}]);
example-controller.test.js
describe('Controller: ExampleCtrl', function() {
beforeEach(module('myModule'));
var ctrl,
mockBackend,
mockState;
var mockExampleResolve = { test: 'Test' };
// Provide any mocks needed
// when do I provide mocks?
beforeEach(function() {
module(function($provide) {
});
});
beforeEach(inject(function($controller, $httpBackend, exampleResolve, $state) {
mockBackend = $httpBackend;
mockState = $state;
exampleResolve = mockExampleResolve;
ctrl = $controller('ExampleCtrl');
}));
describe('initialization', function() {
beforeEach(function() {});
it('should exist', function() {
expect(!!ctrl).toBe(true);
});
it('should initialize any view-model variables', function() {
expect(ctrl.property).toBe('false');
expect(ctrl.resolvedProperty).toEqual({test: 'Test'});
});
});
describe('submit called', function() {
beforeEach(function() {
});
it('should call state.go with the correct arguments', function() {
// how do i check this?
});
});
});

You can use spyOn method from jasmine to check if your parameters are correct.
describe('Controller: ExampleCtrl', function() {
beforeEach(module('myModule'));
var ctrl,
mockBackend,
mockState;
var mockExampleResolve = { test: 'Test' };
// Provide any mocks needed
// when do I provide mocks?
beforeEach(function() {
module(function($provide) {
});
});
beforeEach(inject(function($controller, $httpBackend, exampleResolve, $state) {
mockBackend = $httpBackend;
mockState = $state;
spyOn($state,'go');
exampleResolve = mockExampleResolve;
ctrl = $controller('ExampleCtrl');
}));
describe('initialization', function() {
beforeEach(function() {});
it('should exist', function() {
expect(!!ctrl).toBe(true);
//Here you can pass your param and state
expect($state.go).toHaveBeenCalledWith('anotherView', { prop1: 'yay', prop2: 'again' });
});
it('should initialize any view-model variables', function() {
expect(ctrl.property).toBe('false');
expect(ctrl.resolvedProperty).toEqual({test: 'Test'});
});
});
describe('submit called', function() {
beforeEach(function() {
});
it('should call state.go with the correct arguments', function() {
// how do i check this?
});
});
});

Related

Controller Knows About Service Mock, but Unit Test Doesn't (AngularJS, Karma-Jasmine)

I'm adding unit tests to my AngularJS application, and running into an issue where the controller I'm testing is aware of my mocks (logs the correct mock object), but my unit tests cannot return the mock value. I've been stuck on this one for a while.
Mock Service
angular.module('mocks.myService', [])
.factory('myService', function() {
var service = {
hi : 'hi'
};
return service;
});
Controller
.controller('MyCtrl', ['myService', function(myService) {
var vm = this;
vm.doThing = function() {
console.log(myService);
// Object{hi:'hi'}
};
}]);
Unit Test
describe('myApp.selection module', function() {
var myCtrl, myService;
beforeEach(function() {
myService = module('mocks.myService');
console.log(myService);
// undefined
});
describe('controller', function(){
it('should exist', inject(function($controller) {
myCtrl = $controller('MyCtrl');
expect(myCtrl).toBeDefined();
}));
it ('should do thing', function() {
myCtrl.doThing();
});
});
});
Try this:
describe('myApp.selection module', function () {
var myCtrl, myService;
beforeEach(module("mocks.myService"));
beforeEach(inject(function (_myService_) {
myService = _myService_;
console.log(myService);
}));
describe('controller', function () {
it('should exist', inject(function ($controller) {
myCtrl = $controller('MyCtrl');
expect(myCtrl).toBeDefined();
}));
it('should do thing', function () {
myCtrl.doThing();
});
});
});
source: https://docs.angularjs.org/guide/unit-testing

How to listen to changes on mock service with Jasmine in AngularJS

I'm trying to change the return value of a method in my mock service, but the new method is never called.
The code:
describe('Test 1', function() {
var ctrl, scope, mySrvMock;
beforeEach(function() {
mySrvMock = {
method: function() {
return 'value';
}
}
});
beforeEach(function() {
module('app');
inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('mainCtrl', {
$scop: scope,
mySrv: mySrvMock
});
});
});
it('should return value', function() {
expect(scope.callToMethod).toBe('value') // Pass
});
describe('Test 1.1', function() {
beforeEach(function() {
mySrvMock.method = function() {
return 'different value';
};
});
it('should return different value', function() {
expect(scope.callToMethod).toBe('different value') // Fail (Expected 'value' to be 'different value')
});
});
});
There is a way to listen to the mock service changes?
initialize controller before each it()
describe('Test 1', function() {
var ctrl, scope, mySrvMock, controllerFactory;
beforeEach(function() {
mySrvMock = {
method: function() {
return 'value';
}
}
});
beforeEach(function() {
module('app');
inject(function($rootScope, $controller) {
scope = $rootScope.$new();
controllerFactory = $controller.bind(this, 'mainCtrl',{
$scop: scope,
mySrv: mySrvMock
});
ctrl = controllerFactory();
});
});
describe('Test 2.1', function() {
beforeEach(function() {
ctrl = controllerFactory();
});
it('should return value', function() {
expect(scope.callToMethod).toBe('value') // Pass
});
});
describe('Test 1.1', function() {
beforeEach(function() {
mySrvMock.method = function() {
return 'different value';
};
ctrl = controllerFactory();
});
it('should return different value', function() {
expect(scope.callToMethod).toBe('different value') // Fail (Expected 'value' to be 'different value')
});
});
});

How to test $scope.$watch properly with Jasmine in AngularJS

I'm new to AngularJS and unit testing,
I'm testing a list that gets changing by selected category.
The test is passing but only if I use the httpBackend.expectGET() that expects the XMLHttpRequest from the "getSomethingElse" method.
I also tried to use the scope.$digest() but I got the same results...
The Controller:
app.controller('mainCtrl', ['$scope', 'myService', function($scope,
myService) {
$scope.category = null;
myService.getSomethingElse().then(function(res) {
$scope.somethingElse = res.data;
});
$scope.$watch('category', function() {
if ($scope.category !== null) {
myService.getListByCat($scope.category.name).then(function(res) {
$scope.list = res.data;
});
}
else {
myService.getLongList().then(function(res) {
$scope.list = res.data;
});
}
});
}]);
The Service:
app.service('myService', ['$http', function($http) {
this.getListByCat = function(category) {
return $http.get('getting-list?cat=' + category);
};
this.getLongList = function() {
return $http.get('getting-long-list');
};
this.getSomethingElse = function() {
return $http.get('getting-something-else');
};
}]);
The Test
describe('Testing mainCtrl', function() {
var scope, ctrl;
var myServiceMock = {
getSomethingElse: jasmine.createSpy().and.returnValue(1),
getListByCat: jasmine.createSpy().and.returnValue(2)
};
beforeEach(function() {
module('app');
inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('mainCtrl', {
$scope: scope,
myService: myServiceMock
});
});
});
it('should update the list by selected category', function() {
expect(scope.category).toBeNull();
expect(scope.list).toBeUndefined();
scope.category = {
id: 1,
name: 'Jobs'
};
scope.$apply();
expect(myServiceMock.getSomethingElse).toHaveBeenCalled();
expect(myServiceMock.getListByCat).toHaveBeenCalled();
});
});
The test is passing but only if I use the httpBackend.expectGET() that expects the XMLHttpRequest from the "getSomethingElse" method.
This is because your myServiceMock is not replacing the original myService. You have various ways to test this - one of them is given below. Here we are replacing myService with the service mock:-
beforeEach(function() {
module('app');
module(function($provide){
$provide.factory('myServiceMock',
function(){
return myServiceMock;
);
});
inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('mainCtrl', {
$scope: scope,
myService: myServiceMock
});
});
});
You can add your watcher like this.
$scope.categoryWatcher = categoryWatcher;
$scope.$watch('category', categoryWatcher);
function categoryWatcher() {
if ($scope.category !== null) {
myService.getListByCat($scope.category.name).then(function(res) {
$scope.list = res.data;
});
}
else {
myService.getLongList().then(function(res) {
$scope.list = res.data;
});
}
}
and in Unit testing just create new it construct for that handler
it('should test categoryWatcher for null value', function(){
$scope.category = null;
$scope.categoryWatcher();
// your expectations
});
it('should test categoryWatcher for "desiredValue" value', function(){
$scope.category = "desiredValue";
$scope.categoryWatcher();
// your expectations
});
that way, if&else clauses will be taken in the test.

Spyon angular service method returns Unexpected POST

I have a promise in a controller that I'm trying to test and I'm getting Error: Unexpected request: POST /v1/users.
I'm trying to spyOn the AuthService.changePassword which returns a promise and test whether it got called or not. Not sure why it's actually making the POST call...
controller
angular.module('example')
.controller('ChangePasswordCtrl', ['AuthService', '$state',
function(AuthService, $state) {
var vm = this;
vm.submitted = false;
vm.submit = function(valid) {
vm.submitted = true;
if (!valid) return false;
AuthService.changePassword(vm.email)
.then(function(res) {
$state.go('reset.confirmation');
}, function(err) {
vm.hasError = true;
});
};
}
]);
unit test
describe('ChangePasswordCtrl', function() {
var ctrl, scope, AuthService, $q, $state, deferred;
beforeEach(module('example'));
function _inject() {
inject(function($controller, $rootScope, _AuthService_, _$state_, _$q_) {
scope = $rootScope.$new();
$state = _$state_;
$q = _$q_;
AuthService = _AuthService_;
ctrl = $controller('ChangePasswordCtrl', {
$scope: scope
});
});
}
describe('#submit', function() {
beforeEach(function() {
_inject();
deferred = $q.defer();
spyOn(AuthService, 'changePassword').and.returnValue(deferred.promise);
spyOn($state, 'go');
});
describe('when email address is valid', function() {
it('should call the changePassword method on the AuthService', function() {
ctrl.submit(true);
scope.$digest();
expect(ctrl.submitted).toBe(true);
expect(AuthService.changePassword).toHaveBeenCalled();
});
});
});
});
Your spec code works for me (the real implementation of AuthService.changePassword doesn't get called): http://jsfiddle.net/7W2XB/7/
angular.module('example', [])
.factory('AuthService', function() {
return {
changePassword: function() {
throw new Error('Should not be called');
}
};
})
.controller('ChangePasswordCtrl', ['AuthService',
function(AuthService) {
var vm = this;
vm.submitted = false;
vm.submit = function(valid) {
vm.submitted = true;
if (!valid) return false;
AuthService.changePassword(vm.email)
.then(function(res) {
$state.go('reset.confirmation');
}, function(err) {
vm.hasError = true;
});
};
}
]);
describe('ChangePasswordCtrl', function() {
var ctrl, scope, AuthService, $q, deferred;
function _inject() {
module('ui.router');
module('example');
inject(function($controller, $rootScope, _AuthService_, _$state_, _$q_) {
scope = $rootScope.$new();
$state = _$state_;
$q = _$q_;
AuthService = _AuthService_;
ctrl = $controller('ChangePasswordCtrl', {
$scope: scope
});
});
}
describe('#submit', function() {
beforeEach(function() {
_inject();
deferred = $q.defer();
spyOn(AuthService, 'changePassword').and.returnValue(deferred.promise);
});
describe('when email address is valid', function() {
it('should call the changePassword method on the AuthService', function() {
ctrl.submit(true);
scope.$digest();
expect(ctrl.submitted).toBe(true);
expect(AuthService.changePassword).toHaveBeenCalled();
});
});
});
});
Some questions that might help make the JSFiddle more realistic to your situation: What versions of angular and Jasmine are you using? - How are you defining the AuthService (presumably using angular.factory)?

Angular - mocking out service method that returns a promise

I am trying to test a controller method that relies on a service call to get some data. The service method returns a promise, and I'd like to test the behavior of the controller if the promise is resolved or rejected.
I have come up with this approach to vary the behavior of my mocked service method, but it does not work. The getDataSuccess flag is always true when the mocked getData method is called. Here's what I have so far:
Controller:
app.controller('myController', function($scope, myService) {
myService.getData()
.then(function (data) {
$scope.data = data;
},
function (data) {
$scope.serverError = data;
});
});
Test:
describe('myController', function () {
var ctl, serviceMock, getDataSuccess, scope;
beforeEach(function() {
getDataSuccess = true;
serviceMock = {};
module('app', function ($provide) {
$provide.value('myService', serviceMock);
});
inject(function ($q) {
serviceMock.getData = function () {
var defer = $q.defer();
if (getDataSuccess) {
defer.resolve("theData");
} else {
defer.reject("theData");
}
return defer.promise;
};
});
});
beforeEach(inject(function ($rootScope, $controller, $httpBackend, myService) {
scope = $rootScope.$new();
ctl = $controller('myController', {
$scope: scope,
myService: myService,
});
}));
describe('myController loading data', function () {
it('should set $scope.data if data load succeeds', function () {
getDataSuccess = true;
scope.$apply();
expect(scope.data).toEqual("theData");
});
it('should set $scope.serverError if data load fails', function () {
getDataSuccess = false;
scope.$apply();
expect(scope.serverError).toEqual("theData");
});
});
});
Clearly I'm missing something here. The order of execution is not what I was expecting. What's the proper way to do this sort of thing?
Here's this example in Plunker: http://plnkr.co/edit/ODyslivLorjaLM4EqlEF?p=preview
myService.getData function is called where myController is initialized. So if you want to change the behavior getData function by setting getDataSuccess, you need to initialize myController after you set getDataSuccess true/false.
What I recommend is something like this.
In appSpec.js
describe('myController', function () {
var ctl, serviceMock, getDataSuccess, scope;
beforeEach(function() {
getDataSuccess = true;
serviceMock = {};
module('app', function ($provide) {
$provide.value('myService', serviceMock);
});
inject(function ($q) {
serviceMock.getData = function () {
var defer = $q.defer();
if (getDataSuccess) {
defer.resolve("theData");
} else {
defer.reject("theData");
}
return defer.promise;
};
});
});
beforeEach(inject(function ($rootScope, $controller, $httpBackend, myService) {
scope = $rootScope.$new();
//
// ctl = $controller('myController', {
// $scope: scope,
// myService: myService,
// });
}));
describe('myController loading data', function () {
it('should set $scope.data if data load succeeds', inject(function($controller, myService){
getDataSuccess = true;
ctl = $controller('myController', {
$scope: scope,
myService: myService,
});
scope.$apply();
expect(scope.data).toEqual("theData");
}));
it('should set $scope.serverError if data load fails', inject(function($controller, myService){
getDataSuccess = false;
ctl = $controller('myController', {
$scope: scope,
myService: myService,
});
scope.$apply();
expect(scope.serverError).toEqual("theData");
}));
});
});
This is updated plunker.

Resources