Testing $interval in Jasmine/ Karma - angularjs

I have a simple factory
angular.module('myApp.dice',[]).factory('Dice', ['$interval', function($interval){
return {
rollDice: function(){
return $interval(function(c){
count++;
}, 100, 18, false, 0);
}
};
}]);
In my test case I have
describe('rolling dice', function(){
var promise, promiseCalled = false, notify = false;
beforeEach(inject(function(){
promise = Dice.rollDice();
promise.then(function(){
promiseCalled = true;
});
}));
it('should invoke a promise', inject(function(){
expect(promise).toBeDefined();
}));
it('should set promiseCalled', inject(function($rootScope, $interval){
expect(promiseCalled).toEqual(true);
}));
});
How do I trigger the interval or resolve the promise? to get the test to be true?

See plunker
You need to use '$interval.flush' from angularjs-mocks and then test for the result. I've taken the liberty to assign count to the dice object because it's undefined in your code sample.
Is there any reason why $interval is called with invokeApply = false? because then you have to manually call $apply.
The test case would be:
describe('rolling dice', function(){
var $interval, $rootScope, dice;
beforeEach(module('plunker'));
beforeEach(inject(function(_$interval_, _$rootScope_, _Dice_){
$interval = _$interval_;
$rootScope = _$rootScope_;
dice = _Dice_;
}));
it('should increment count', inject(function(){
dice.rollDice();
// Move forward by 100 milliseconds
$interval.flush(100);
// Need to call apply because $interval was called with invokeApply = false
$rootScope.$apply();
expect(dice.count).toBe(1);
}));
});
with factory:
app.factory('Dice', ['$interval', function($interval){
var self= {
count: 0,
rollDice: function(){
return $interval(function() {
self.count++;
}, 100, 18, false, 0);
}
};
return self;
}]);
EDIT:
I prefer testing the result of a function call but perhaps you have a use case for testing that a promise function is called so this might be what you're looking for. From the angular docs on $interval it says it returns
A promise which will be notified on each iteration.
Keyword being notified. And from the promise API the arguments for then are
then(successCallback, errorCallback, notifyCallback)
i.e. the third call back function notifyCallback is called for each iteration of $interval. So the test would look something like this:
it('should set promiseCalled', function(){
var promiseCalled = false;
dice.rollDice().then(null, null, function() {
promiseCalled = true;
});
$interval.flush(100);
$rootScope.$apply();
expect(promiseCalled).toBe(true);
});
If you want to test for a resolved promise, then it would look something like this:
it('should resolve promise', function(){
var promiseCalled = false;
var resolvedData = null;
$q.when('Some Data').then(function(data) {
promiseCalled = true;
resolvedData = data;
});
$rootScope.$apply();
expect(promiseCalled).toBe(true);
expect(resolvedData).toBe('Some Data');
});
I've updated the plunker with these test cases.

Related

How to write mock in karma-jasmine for AngularJS unit testing

I have to unit test my controller. First I have to create mock for my services.
Here is my service:
angular.module("demo-app")
.factory("empService",function($http){
var empService={};
empService.getAllEmployees=function(){
return $http.get("http://localhost:3000/api/employees");
}
empService.postEmployee=function(emp){
return $http.post("http://localhost:3000/api/employees",emp);
}
empService.getEmployee=function(id){
return $http.get("http://localhost:3000/api/employees/"+id)
}
empService.putEmployee=function(emp){
return $http.put("http://localhost:3000/api/employees/"+emp._id,emp)
}
empService.deleteEmployee=function(id){
return $http.delete("http://localhost:3000/api/employees/"+id);
}
empService.findEmployee=function(emp){
return $http.post("http://localhost:3000/api/employees/search",emp);
}
return empService;
})
Here is findData() method in my controller, which I am going to test:
$scope.findData=function(){
$scope.loadingEmployee=true;
var emp={};
listProp=Object.getOwnPropertyNames($scope.searchEmployee);
for(index in listProp){
if($scope.searchEmployee[listProp[index]]!=""){
emp[listProp[index]]=$scope.searchEmployee[listProp[index]];
}
}
console.log(emp);
empService.findEmployee(emp).then(function(data){
$scope.allEmployees=data.data;
console.log(data.data);
$scope.loadingEmployee=false;
});
}
How can I mock my empService.findEmployee(emp) method, so that I can test the findData() method.
My spec.js test file with mocking my service method. Here it is:
beforeEach(function(){
var emp={"name":"sanjit"};
fakeService={
getAllEmployees:function(emp){
def=q.defer();
def.resolve({data:[{"name":"sanjit"},{'name':'ssss'}]});
return def.promise;
},
findEmployee:function(emp){
var def=q.defer();
def.resolve({data:[{"name":"sanjit"}]});
console.log("working");
return def.promise;
}
};
spyOn(fakeService,'findEmployee').and.callThrough();
fakeService.findEmployee(emp);
});
beforeEach(angular.mock.inject(function($rootScope,$controller,$injector,$q){
httpBackend=$injector.get('$httpBackend');
scope=$rootScope.$new();
q=$q;
ctrl=$controller('adminEmployeeCtrl',{$scope:scope,empService:fakeService});
}));
it('findData test',function(){
scope.$apply();
scope.findData();
expect(scope.loadingEmployee).toEqual(false);
})
But I got another error:
Error: Unexpected request: GET dashboard/views/dashboard-new.html
No more request expected
But I didn't call it. Please help me
You may not have manually called GET dashboard/views/dashboard-new.html but $scope.$apply() might be triggering it somehow and you can't do anything but handle it.
You can do something like this to handle it: (after injecting it using _$httpBackend_ and assigning to $httpBackend in beforeEach)
$httpBackend.when('GET', 'dashboard/views/dashboard-new.html').respond(200);
scope.$digest();
$httpBackend.flush();
One of the most important rules when testing controllers in angularjs is you do not need to create reall http requests, just mock the functions in that service that are used by your controller. So you need to spyOn them and call fake function to return the proper value. Let's spy on one of them
/**
* #description Tests for adminEmployeeCtrl controller
*/
(function () {
"use strict";
describe('Controller: adminEmployeeCtrl ', function () {
/* jshint -W109 */
var $q, $scope, $controller;
var empService;
var errorResponse = 'Not found';
var employeesResponse = [
{id:1,name:'mohammed' },
{id:2,name:'ramadan' }
];
beforeEach(module(
'loadRequiredModules'
));
beforeEach(inject(function (_$q_,
_$controller_,
_$rootScope_,
_empService_) {
$q = _$q_;
$controller = _$controller_;
$scope = _$rootScope_.$new();
empService = _empService_;
}));
function successSpies(){
spyOn(empService, 'findEmployee').and.callFake(function () {
var deferred = $q.defer();
deferred.resolve(employeesResponse);
return deferred.promise;
// shortcut can be one line
// return $q.resolve(employeesResponse);
});
}
function rejectedSpies(){
spyOn(empService, 'findEmployee').and.callFake(function () {
var deferred = $q.defer();
deferred.reject(errorResponse);
return deferred.promise;
// shortcut can be one line
// return $q.reject(errorResponse);
});
}
function initController(){
$controller('adminEmployeeCtrl', {
$scope: $scope,
empService: empService
});
}
describe('Success controller initialization', function(){
beforeEach(function(){
successSpies();
initController();
});
it('should findData by calling findEmployee',function(){
$scope.findData();
// calling $apply to resolve deferred promises we made in the spies
$scope.$apply();
expect($scope.loadingEmployee).toEqual(false);
expect($scope.allEmployees).toEqual(employeesResponse);
});
});
describe('handle controller initialization errors', function(){
beforeEach(function(){
rejectedSpies();
initController();
});
it('should handle error when calling findEmployee', function(){
$scope.findData();
$scope.$apply();
// your error expectations
});
});
});
}());

AngularJS Testing - Testing Out Logic in Promise Callback

I have the following function in my controller:
$scope.submitNote = function(){
myService.addNote($scope.note).then(function(data){
if(data.success === true){
$scope.note = null;
}
else{
// API call failed
}
}, function(){
// Promise call failed
});
};
I set up my testing environment with:
// Mock out fake service
beforeEach(function(){
myService = {
addNote: function(){
deferred = q.defer();
deferred.resolve({
success: true
});
return deferred.promise;
}
};
spyOn(myService, 'addNote').and.callThrough();
});
// Assign controller scope
beforeEach(inject(function($controller, $rootScope, $q){
q = $q;
scope = $rootScope.$new();
$controller('myController', {
$scope: scope,
myService: myService
});
}));
Then test out my submitNote() function with:
describe('submitNote Test', function(){
it('should set scope.note to null after successful service call', function(){
scope.submitNote();
expect(myService.addNote).toHaveBeenCalled();
expect(scope.note).toBe(null);
});
});
The first expect passes, but the second expect does not. It looks like the then() callback from my submitNote() function isn't being called in the test.
How do I make sure the promise callback in the original function is called?
To give you cleaner tests that you have more control over the ngMock module extends various core services so they can be inspected and controlled in a synchronous manner.
Promise callbacks are executed during the digest loop, which in your testing environment you need to start manually.
For example:
describe('submitNote Test', function () {
it('should set scope.note to null after successful service call', function () {
scope.submitNote();
scope.$digest();
expect(myService.addNote).toHaveBeenCalled();
expect(scope.note).toBe(null);
});
});

Testing an asynchronous function in an angular factory

Im new to testing and im trying to test my angular code in Jasmine. Im stuck on the problem of testing the answer from an resolved promise. Right now the test gets timed out. I would like to have the test waiting for the respons instead of just put in a mockup respons. How do i do that? Is it a bad way of making unit-tests?
angular.module("Module1", ['ng']).factory("Factory1", function($q){
function fn1(){
var deferred = $q.defer();
setTimeout(function(){ deferred.resolve(11); }, 100); // this is representing an async action
return deferred.promise;
}
return { func1 : fn1 };
});
describe('test promise from factory', function() {
var factory1, $rootScope, $q;
beforeEach(module('Module1'));
beforeEach(inject(function(Factory1, _$rootScope_, _$q_) {
factory1=Factory1;
$rootScope = _$rootScope_;
$q = _$q_;
}));
it('should be get the value from the resolved promise', function(done) {
factory1.func1().then(function(res){
expect(res).toBe(11);
done(); // test is over
});
$rootScope.$digest();
});
});
The setTimeout() block represents an async function call, and i dont want to replace it with something like $timeout.
I don't know why you wouldn't want to use the $timeout service.
But if you really want to use the setTimeout, a $rootScope.$digest() is required inside the callback.
function fn1() {
var deferred = $q.defer();
// this is representing an async action
setTimeout(function() {
deferred.resolve(11);
$rootScope.$digest(); // this is required ($timeout do this automatically).
}, 100);
return deferred.promise;
}

How do I test a decorator which is a wrapper around $timeout?

I have the following decorator which wraps around the original $timeout from within $rootScope. When used inside controllers, it will help by canceling the $timeout promise when the scope is destroyed.
angular.module('MyApp').config(['$provide', function ($provide) {
$provide.decorator('$rootScope', ['$delegate', function ($delegate) {
Object.defineProperty($delegate.constructor.prototype, 'timeout', {
value: function (fn, number, invokeApply) {
var $timeout = angular.injector(['ng']).get('$timeout'),
promise;
promise = $timeout(fn, number, invokeApply);
this.$on('$destroy', function () {
$timeout.cancel(promise);
});
},
enumerable: false
});
return $delegate;
}]);
}]);
But how do I properly unit test this? I kinda see 2 tests I should be doing here... 1) Check if the original $timeout was called when $rootScope.timeout() is called and 2) check if the promise is cancelled when the scope is destroyed.
Here's my current test suite for this:
describe('MyApp', function () {
var $rootScope;
beforeEach(function () {
module('MyApp');
inject(['$rootScope', function (_$rootScope_) {
$rootScope = _$rootScope_;
}]);
});
describe('$timeout', function () {
it('<something>', function () {
$rootScope.timeout(function () {}, 2500);
// Test if the real $timeout was called with above parameters
$rootScope.$destroy();
// Test if the $timeout promise was destroyed
});
});
});
The only thing that this does is giving me 100% coverage. But that's not what I want... How do I properly test this?
Since nobody was able to help me and I really wanted to have this done, I eventually found my own solution. I'm not sure it is the best solution but I think it does the trick.
Here's how I solved it:
describe('MyApp', function () {
var $rootScope,
$timeout,
deferred;
beforeEach(function () {
module('MyApp');
inject(['$rootScope', '$q', function (_$rootScope_, _$q_) {
$rootScope = _$rootScope_;
deferred = _$q_.defer();
}]);
$timeout = jasmine.createSpy('$timeout', {
cancel: jasmine.createSpy('$timeout.cancel')
}).and.returnValue(deferred.promise);
spyOn(angular, 'injector').and.returnValue({
get: function () {
return $timeout;
}
});
});
describe('$timeout', function () {
it('should set the timeout with the specified arguments', function () {
$rootScope.timeout(angular.noop, 250, false);
expect($timeout).toHaveBeenCalledWith(angular.noop, 250, false);
});
it('should cancel the timeout on scope destroy event', function () {
$rootScope.timeout(angular.noop, 250, false);
$rootScope.$destroy();
expect($timeout.cancel).toHaveBeenCalledWith(deferred.promise);
});
});
});

code coverage for angularJS service

I have a controller that is calling a service. I want to write my unit tests such that I get coverage on the success and error functions of the then function.
maApp.controller('editEmailAndPasswordController',
["$scope", "emailAndPasswordService",
function editEmailAndPasswordController($scope, emailAndPasswordService) {
$scope.EmailId = 'as#as.com';
$scope.CurrentPassword = '';
$scope.Success = false;
$scope.save = function () {
var request = {
currentPassword: $scope.CurrentPassword,
newEmailId: $scope.EmailId
};
emailAndPasswordService.save(request).then(function (data) {
$scope.Success = true;
}, function (data, status, header, config) {
$scope.Success = false;
});
};
}]);
This is what I have got so for. I want another test for the fail condition as well, but not sure how to set up the mock service.
describe('Controllers', function () {
var $scope, ctrl, controller, svc, def;
describe('editEmailAndPasswordController', function () {
beforeEach(function() {
module('maApp');
});
beforeEach(inject(function ($controller, $rootScope, $q) {
ctrl = $controller;
svc = {
save: function () {
def = $q.defer();
return def.promise;
}
};
spyOn(svc, 'save').andCallThrough();
$scope = $rootScope.$new();
controller = ctrl('editEmailAndPasswordController', { $scope: $scope, emailAndPasswordService: svc });
}));
it('should set ShowEdit as false upon save', function () {
$scope.ShowEdit = true;
$scope.EmailId = 'newEmail';
$scope.CurrentPassword = 'asdf1';
$scope.save();
expect($scope.EmailId).toBe('as#as.com');
expect($scope.Success).toBe(true);
});
});
});
You have some real problems with this code.
Don't call ".andCallThrough()"- that way your test depends on the implementaton of the service and means your controller is not isolated. The main idea is to create unit tests.
svc = {save: jasmine.createSpy()};
svc.save.andReturn(...);
You can't assert against expect($scope.EmailId).toBe('as#as.com'); because you change the value in the code to $scope.EmailId = 'newEmail';
you can create 2 private methods for readability
function success(value) {
var defer = q.defer();
defer.resolve(value);
return defer.promise;
}
function failure(value){
var defer = q.defer();
defer.reject(value);
return defer.promise;
}
Thus in the first test you can call
svc.save.andReturn(success());
$scope.$digest()
expect($scope.Success).toBeTruthy();
And in the other test you will have the same but:
svc.save.andReturn(failure());
$scope.$digest()
expect($scope.Success).toBeFalsy();
In one case, you want the promise to be successful, so you want to resolve the deferred:
$scope.save();
def.resolve('whatever');
$scope.$apply();
expect($scope.Success).toBe(true);
...
In the other case, you want the promise to be a failure, so uou want to reject the deferred:
$scope.save();
def.reject('whatever');
$scope.$apply();
expect($scope.Success).toBe(false);
...
This is explained in the documentation.

Resources