How can I spy on a mock service AngularJS / Karma? - angularjs

'use strict'
webApp.controller 'NavigationController', [
'$scope'
'$rootScope'
'UserService'
($scope, $rootScope, UserService) ->
$scope.init = ->
UserService.isAuthenticated().then (authenticated) ->
$scope.isAuthenticated = authenticated
$scope.init()
]
I want to write a test to spyOn if isAuthenticated was called from UserService. In my beforeEach, I have:
beforeEach ->
module 'webApp'
inject ($injector) ->
$httpBackend = $injector.get '$httpBackend'
$q = $injector.get '$q'
$rootScope = $injector.get '$rootScope'
$scope = $rootScope.$new()
$controller = $injector.get '$controller'
UserServiceMock =
isAuthenticated: ->
deferred = $q.defer()
deferred.promise
controller = $controller 'AboutUsController',
'$scope': $scope
'$rootScope': $rootScope
'UserService': UserServiceMock
$httpBackend.whenGET('/api/v1/session').respond 200
Any help would be appreciated.. thanks

You can just set a variable to true when isAuthenticated is called in your UserServiceMock. e.g.:
var isAuthenticatedCalled;
var controller;
beforeEach(function() {
isAuthenticatedCalled = false;
module('webApp');
inject(function($injector) {
//...
UserServiceMock = {
isAuthenticated: function() {
isAuthenticatedCalled = true;
var deferred = $q.defer();
deferred.resolve();
return deferred.promise;
}
};
controller = $controller('AboutUsController', {
'$scope': $scope,
'$rootScope': $rootScope,
'UserService': UserServiceMock
});
// ...
});
});
it('should call isAuthenticated', function() {
expect(isAuthenticatedCalled).toBe(true)
});
Alternatively you could use Jasmine's spyOn function.
UserServiceMock = {
isAuthenticated: function() {
var deferred = $q.defer();
deferred.resolve();
return deferred.promise;
}
};
spyOn(UserServiceMock, 'isAuthenticated');
And in your test you can do
it('should call isAuthenticated', function() {
expect(UserServiceMock.isAuthenticated).toHaveBeenCalled()
});

Related

angularjs unit testing (cannot find propery of 'resolve' undefined angularjs testing)

I am trying to do unit test of my angular app with karma. I am getting some error. Am i missing something? A
This my controller
(function () {
"use strict"
angular
.module("myApp")
.controller("userCtrl",['$scope', '$state', 'userService', 'appSettings','md5','currentUser','$rootScope',
function ($scope, $state, userService, appSettings,md5,currentUser, $rootScope) {
$scope.login = function() {
$scope.loading = true;
if($scope.password != null){
var user ={
username:$scope.username,
password:md5.createHash($scope.password)
}
var getData = userService.login(user);
getData.then(function (response) {
console.log(response);
$scope.loading = false;
currentUser.setProfile(user.username, response.data.sessionId);
$state.go('videos');
}, function (response) {
console.log(response.data);
});
}else{
$scope.msg = "Password field is empty!"
}
}
}])
}());
This is my test codes
'use strict';
describe('userCtrl', function() {
beforeEach(module('myApp'));
var scope, userCtrl, apiService,q, deferred, currentUser;
describe('$scope.login', function(){
beforeEach(function(){
apiService = {
login: function () {
deferred = q.defer();
return deferred.promise;
};
};
});
beforeEach(inject(function($controller, $rootScope, $q, _currentUser_){
var user ={name:'ali',password:'password'};
scope = $rootScope.$new();
q = $q;
// The injector unwraps the underscores (_) from around the parameter names when matching
userCtrl = $controller('userCtrl', {
$scope:scope,
userService:apiService
});
//userService = _userService_;
currentUser = _currentUser_;
}));
it('should call user service login', function() {
spyOn(apiService, 'login').and.callThrough();
scope.login();
deferred.resolve(user);
expect(apiService.login).toHaveBeenCalled();
});
it('checks the password field', function() {
scope.login();
expect(scope.msg).toEqual('Password field is empty!');
});
});
});
And i am getting this error
enter image description here
If you have to test controller then use to spyon for service method and in case of service then use HttpBackend
describe('Testing a Controller that uses a Promise', function() {
var $scope;
var $q;
var deferred;
beforeEach(module('search'));
beforeEach(inject(function($controller, _$rootScope_, _$q_, searchService) {
$q = _$q_;
$scope = _$rootScope_.$new();
// We use the $q service to create a mock instance of defer
deferred = _$q_.defer();
// Use a Jasmine Spy to return the deferred promise
spyOn(searchService, 'search').and.returnValue(deferred.promise);
// Init the controller, passing our spy service instance
$controller('SearchController', {
$scope: $scope,
searchService: searchService
});
}));
it('should resolve promise', function() {
// Setup the data we wish to return for the .then function in the controller
deferred.resolve([{
id: 1
}, {
id: 2
}]);
// We have to call apply for this to work
$scope.$apply();
// Since we called apply, not we can perform our assertions
expect($scope.results).not.toBe(undefined);
expect($scope.error).toBe(undefined);
});
});
This for same using spyon for service method then use $appy method to make it work.

Testing a controller that execute a service with $resource response

I have a controller that use a service, resolve a promise and then set a variable.
I want to test if the variable has been set, How I do this?
Service.js
angular
.module('myModule')
.service('MyService',['$resource',function($resource){
var r = $resource('/users/:id',{id:'#id'});
this.post = function(_user){
return (new r(_user)).$save()
}
}])
Controller
angular
.module('myModule')
.controller('myCtrl', ['MyService',function(MyService){
var vm = this;
vm.value = 0;
vm.post = function(_user){
MyService.post(_user).then(function(){
vm.value = 1;
});
};
}])
controller_spec
describe('Test Controller',function(){
beforeEach(module('myModule'));
beforeEach(inject(function($controller, MyService){
this.ctrl = $controller('myCtrl');
}))
it('after save value is 1',function(){
this.ctrl.post({name: 'chuck'});
//Always this.ctrl.value it's equal to 0
expect(this.ctrl.value).toBeEqual(1);
});
});
mock the service method and return a promise, then resolve it when you need it.
describe('Test Controller',function(){
var postDefer;
beforeEach(module('myModule'));
beforeEach(inject(function($controller, MyService, $q){
postDefer = $.defer();
spyOn(MyService, 'post').and.returnValue(postDefer.promise);
this.ctrl = $controller('myCtrl', {MyService : MyService});
}))
it('after save value is 1',function(){
this.ctrl.post({name: 'chuck'});
postDefer.resolve();
scope.$apply();
expect(this.ctrl.value).toBeEqual(1);
});
});

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.

Why won't $scope be found in my AngularJS / Karma unit test?

My controller:
angularMoonApp.controller('SourceController', ['$scope', '$rootScope', '$routeParams', 'fileService', function ($scope, $rootScope, $routeParams, fileService) {
$scope.init = function() {
$rootScope.currentItem = 'source';
fileService.getContents($routeParams.path).then(function(response) {
$scope.contents = response.data;
$scope.fileContents = null;
if(_.isArray($scope.contents)) {
// We have a listing of files
$scope.breadcrumbPath = response.data[0].path.split('/');
} else {
// We have one file
$scope.breadcrumbPath = response.data.path.split('/');
$scope.breadcrumbPath.push('');
$scope.fileContents = atob(response.data.content);
fileService.getCommits(response.data.path).then(function(response) {
$scope.commits = response.data;
});
}
});
}
$scope.init();
}]);
My unit test:
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, $routeParams, $q, createController, fileService, deferred;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$routeParams = $injector.get('$routeParams');
$scope = $rootScope.$new();
$q = $injector.get('$q');
deferred = $q.defer();
fileService = $injector.get('fileService');
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope,
'$routeParams': $routeParams,
'fileService': fileService
});
};
}));
it("should set the current menu item to 'source'", function() {
createController();
$scope.init();
expect($rootScope.currentItem).toBe('source');
});
it("should get test the getContents call of the fileService", function() {
spyOn(fileService, 'getContents').andCallThrough();
createController();
$scope.init();
expect(fileService.getContents).toHaveBeenCalled();
});
it("should return an object with multiple files", function() {
var multipleFiles = [{path: '.DS_Store'}, {path: '.bowerrc'}];
deferred.resolve(multipleFiles);
spyOn(fileService, 'getContents').andReturn(deferred.promise);
createController();
$scope.init();
expect($scope.contents).toBe(multipleFiles);
expect($scope.breadcrumbPath).toBe('');
});
});
})();
The last test fails with:
Expected undefined to be [ { path : '.DS_Store' }, { path : '.bowerrc' } ].
Expected undefined to be ''.
Why is the $scope undefined here?
Your controller is expecting you to inject in $rootScope which you are not doing in your unit test.
You have:
createController = function() {
return $controller('SourceController', {
'$scope': $scope,
'$routeParams': $routeParams,
'fileService': fileService
});
But but should have:
createController = function() {
return $controller('SourceController', {
'$scope': $scope,
'$rootScope': $rootScope,
'$routeParams': $routeParams,
'fileService': fileService
});
Also, you will want to call this code:
createController();
$scope.init();
before you resolve your promise:
deferred.resolve(multipleFiles);
The scope is not undefined. What is undefined is $scope.contents and $scope.breadcrumbPath.
And that's because promise callbacks are always being called asynchronously. You need to call
$scope.$apply()
before verifying your expectations.

Resources