Catching variable set in timeout in Angular/Jasmine test - angularjs

In this Angular/Jasmine example I have a controller with a promise and a $timeout.
The test fails because a variable set in the $timeout is undefined. Other variables not set inside the $timeout don't have this problem.
Only this expect fails, the others work:
expect($scope.notes).toBe(notes);
I used $timeout.flush() to wait but it is ignored. Any ideas how to fix this?
PLUNK http://plnkr.co/edit/Jet3KRs7baTIzk8L30JQ
angular.module("mymodule", [])
.service('myHttp', function($http){
this.call = function($q) {
var defer = $q.defer();
$http({url:"gothere"})
.then(function (response) {
defer.resolve(response);
});
return defer.promise;
};
})
.controller('ctl', function($scope,$timeout,myHttp) {
$scope.read = function (id){
var data = {};
data.id = id;
myHttp.call({url:'/getRecord', data:data})
.then(function(response) {
$scope.id = response.id;
$scope.name = response.nm;
$scope.descrip = response.dsc;
$timeout(function(){
$scope.notes = response.nts;
},1000);
});
};
});
describe('Testing a Controller that uses a Promise', function () {
var $scope;
var $q;
var deferred;
beforeEach(module('mymodule'));
beforeEach(inject(function($controller, _$rootScope_, _$q_, $timeout, myHttp) {
$scope = _$rootScope_.$new();
$q = _$q_;
deferred = $q.defer();
spyOn(myHttp, 'call').and.returnValue(deferred.promise);
$controller('ctl', {
$scope: $scope,
$timeout: $timeout,
myHttp: myHttp
});
$scope.read(1)
$timeout.flush(2000);
}));
it('should resolve promise', function () {
var id = 1;
var name = "John";
var descrip = "This is the description";
var notes = "These are the notes";
var obj = {
id: id,
nm: name,
dsc: descrip,
nts: notes
};
deferred.resolve(obj);
$scope.$apply();
expect($scope.id).toBe(id);
expect($scope.name).toBe(name);
expect($scope.descrip).toBe(descrip);
expect($scope.notes).toBe(notes);
});
});

Give a timeout to read the variable :
it("should be proper after timeout", function(){
expect($scope.notes).toBe(notes);
}, 1000)

The answer is: use $timeout.flush() after $scope.apply();
$scope.$apply();
$timeout.flush();

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.

Controller not invoking then with mock service's deferred

I am trying to mock a method in a service that returns a promise. The controller:
app.controller('MainCtrl', function($scope, foo) {
var self = this;
this.bar = "";
this.foobar = function() {
console.log('Calling the service.');
foo.fn().then(function(data) {
console.log('Received data.');
self.bar = data;
});
}
this.foobar();
});
The spec file:
angular.module('mock.foo', []).service('foo', function($q) {
var self = this;
this.fn = function() {
console.log('Fake service.')
var defer = $q.defer();
defer.resolve('Foo');
return defer.promise;
};
});
describe('controller: MainCtrl', function() {
var ctrl, foo, $scope;
beforeEach(module('app'));
// inject the mock service
beforeEach(module('mock.foo'));
beforeEach(inject(function($rootScope, $controller, _foo_) {
foo = _foo_;
$scope = $rootScope.$new();
ctrl = $controller('MainCtrl', {$scope: $scope , foo: foo });
}));
it('Should call foo fn', function() {
expect($scope.bar).toBe('Foo');
});
});
When debugging, I can see in the controller the promise object state being 1 (resolved). Yet, the success callback within then is never invoked.
The following Plunker http://plnkr.co/edit/xpiPKPdjhiaI8KEU1T5V reproduces the scenario. Any help would be greatly appreciated.
You must get a reference to $rootScope in your test and call:
$rootScope.$digest()
Your plunk, revisited:
$digest called and test passed.
Also your mock didn't return anything in the resolve, I added:
defer.resolve('Foo');
HTH

Jasmine Testing Controller and mock returned data in Service Promise

i have a problem when mocking a data service promise.
I use AngularJS and Jasmine 2.2.0
My Code is:
Controller
app.controller('homeController', ['$scope', 'ngAppSettings', 'homeViewModel', 'storageService', 'homeService',
function ($scope, ngAppSettings, homeViewModel, storageService, homeService) {
$scope.applicationName = ngAppSettings.applicationName;
$scope.model = homeViewModel;
storageService.getStorageAuth().then(function (data) {
$scope.model.userName= data.name;
}, function (err) {
alert(err.errors[0].message);
});
}]);
Service
app.service('storageService', ['$q', 'ngAppSettings', 'localStorageService',
function ($q, ngAppSettings, localStorageService) {
this.getStorageAuth = function () {
var deferred = $q.defer();
var userAuth = localStorageService.get(ngAppSettings.storageUser);
if (userAuth) {
data = userAuth;
}
deferred.resolve(data);
return deferred.promise;
};
}]);
Test Spec
describe('Controllers: homeController', function () {
var $rootScope;
var $scope;
var ctrl;
var $q;
var deferred;
var storageService;
beforeEach(module('IspFrontEndTemplateApp'));
beforeEach(inject(function (_$q_, _storageService_) {
$q = _$q_;
storageService = _storageService_;
deferred = $q.defer();
spyOn(storageService, "getStorageAuth").and.returnValue(deferred.promise);
}));
beforeEach(inject(function (_$rootScope_, $controller) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
ctrl = $controller('homeController', { $scope: $scope });
}));
it('UserName is: UsuarioTeste', function () {
deferred.resolve({
isAuth: true,
userName: "UsuarioTeste",
name: "UsuarioTeste"
});
expect($scope.model.userName).toBe('UsuarioTeste');
});
});
The error is: Expected '' to be 'UsuarioTeste'.
I need test my model properties in controller but the value is not refreshed
You need to $digest one more time in your test:
$scope.$digest();
expect($scope.model.userName).toBe('UsuarioTeste'); // will work
The reason is that promises are resolved asynchronously in the sense that a promise never calls its success or error callbacks within the same $apply it has been created in, even if it has been resolved. Code could be hard to read if you create a promise in the beginning of your function and the effect of the resolved promise would be already observable a few lines later.

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)?

Promise not resolving in angular jasmine unit test

My service looks like this:
app.service('foo',function($q){
this.fn3 = function(){
var deferred = $q.defer();
return deferred.promise;
}
});
My spec document is as below:
describe('Testing: MainCtrl', function() {
var $scope = null;
var ctrl = null;
var mockfoo;
var deferred;
var $q;
var data = {name: 'test'};
var createController;
beforeEach(module('plunker'));
beforeEach(inject(function($rootScope, $controller,_$q_) {
$scope = $rootScope.$new();
$q = _$q_;
mockfoo = {
fn3: function(){ }
};
deferred = $q.defer();
spyOn(mockfoo,'fn3').and.returnValue(deferred.promise);
createController = function() {
return $controller('MainCtrl', {
$scope: $scope,
foo: mockfoo
});
};
}));
it('Should call fn3()', function(){
ctrl = createController();
deferred.resolve(data);
$scope.$digest();
expect(mockfoo.fn3).toHaveBeenCalled();
expect($scope.temp).toBe(data);
});
});
I am not quiet sure why this spec is failing. I am resolving the promise and then calling $digest which means that $scope.temp should get a value.
I have a plnkr for this here:
http://plnkr.co/edit/uzWqTT
Any suggestions anybody ?
You have to catch the resolved value by accessing the value in the then function:
var resolvedValue;
$scope.temp.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toEqual(data);
See plunker.

Resources