Unit testing for function - javascript

I’m writing a unit test for function getNextPage().
I set the test: expect(this.anotherService.resources).toEqual(3);
I got error: Expected undefined to equal 3 when running the test.
I logged the anotherService.resources and it returned 3 in console.
Not sure why it's not working.
Test
describe('Test for someController', function() {
beforeEach(function() {
module('someApp');
return inject(function($injector) {
var $controller;
var $q = $injector.get('$q');
this.rootScope = $injector.get('$rootScope');
$controller = $injector.get('$controller');
this.state = $injector.get('$state');
this.stateParams = {
id: 1,
}
this.location = $injector.get('$location')
this.timeout = $injector.get('$timeout')
this.upload = $injector.get('$upload')
this.someService = {
getItemList: function(res) {
var deferred = $q.defer();
deferred.resolve({
data: {
totalRows: 2,
rows: 3,
}
});
return deferred.promise;
},
pages: jasmine.createSpy(),
memberIds: 1,
currEng: []
};
this.anotherService = {
resources: {}
};
this.scope = this.rootScope.$new();
this.controller = $controller('someController', {
'$scope': this.scope,
'$rootScope': this.rootScope,
'$state': this.state,
'$stateParams': this.stateParams,
'$location': this.location,
'$timeout': this.timeout,
'$upload': this.upload,
'someService': this.someService,
});
this.scope.$digest();
});
});
it('should be defined', function() {
expect(this.controller).toBeDefined();
expect(this.scope.ss).toEqual(this.someService);
});
it('should run the getNextPage function', function() {
this.scope.getNextPage();
this.scope.$digest();
console.log(this.anotherService.resources); // this is showing as Object {} in terminal
expect(this.anotherService.resources).toEqual(3);
});
Code:
someapp.controller('someController', resource);
resource.$inject = ['$scope', '$state', '$stateParams', '$location','$timeout','$upload', 'someService', 'anotherService'];
function resource($scope, $state, $stateParams,$location,$timeout, $upload, someService, anotherService) {
$scope.fileReaderSupported = window.FileReader != null && (window.FileAPI == null || FileAPI.html5 != false);
$scope.ss = EsomeService;
$scope.as = anotherService;
$scope.getNextPage = getNextPage;
function getNextPage(options){
var o = options || {selected:1};
var start = (o.selected-1)*10 || 0;
someService.currPage = o.selected;
someService.getItemList($stateParams.id,'F', start).then(function (res){
anotherService.resources = res.data.rows;
console.log(anotherService.resources) // this shows LOG: 3 in terminal
someService.numResults = res.data.totalRows;
someService.pageNumbers = someService.pages(res.data.totalRows,10);
})
}
});

The value of this.anotherService.resources is still {} in your test because the code in the following then callback is executed after your test runs, asynchronously:
someService.getItemList($stateParams.id,'F', start).then(function (res){
anotherService.resources = res.data.rows;
console.log(anotherService.resources)
someService.numResults = res.data.totalRows;
someService.pageNumbers = someService.pages(res.data.totalRows,10);
})
Although in getItemList you resolve the promise synchronously
getItemList: function(res) {
var deferred = $q.defer();
deferred.resolve({
data: {
totalRows: 2,
rows: 3,
}
});
return deferred.promise;
},
... it actually does not call the then function on the promise immediately when you call deferred.resolve. When you think of it, that would not make sense either, because the promise must first be returned to the caller before the caller can attach the then call back to it. Instead it calls the then callback asynchronously, i.e. after all currently executing code finishes with an empty call stack. This includes your test code! As stated in the Angular documentation:
then(successCallback, errorCallback, notifyCallback) – regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available.
and also in the testing example in the same documentation:
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
How to test asynchronous code
First, you could let getNextPage return a promise -- the same promise that getItemList returns:
function getNextPage(options){
var o = options || {selected:1};
var start = (o.selected-1)*10 || 0;
someService.currPage = o.selected;
// store the promise in a variable
var prom = someService.getItemList($stateParams.id,'F', start);
prom.then(function (res){
anotherService.resources = res.data.rows;
console.log(anotherService.resources) // this shows LOG: 3 in terminal
someService.numResults = res.data.totalRows;
someService.pageNumbers = someService.pages(res.data.totalRows,10);
});
return prom; // return that promise
}
And then you can use then on getNextPage(), which will execute in sequence with any other then callbacks attached to it, so after the then callback in the above piece of code.
Jasmine's done can then be used to tell Jasmine the test is asynchronous and when it has completed:
// The presence of the `done` parameter indicates to Jasmine that
// the test is asynchronous
it('should run the getNextPage function', function(done) {
this.scope.getNextPage().then(function () {
this.scope.$digest();
console.log(this.anotherService.resources);
expect(this.anotherService.resources).toEqual(3);
done(); // indicate to Jasmine that the asynchronous test has completed
});
});

Related

Testing promise in Jasmine and not resolving- getting Expected undefined to be object

I am new to testing and I am testing some angular code with jasmine. The mock promise I have created isn't resolving. Here is the block of code that I am testing:
$scope.init = function() {
EngageService.getGrouping($stateParams.gid).then(function(res) {
$scope.grouping = res.data;
$scope.parent_id = $scope.grouping.parent != null ? $scope.grouping.parent.id : null;
$scope.startDate = $scope.grouping.startDate ? new Date($scope.grouping.startDate) : new Date()
$scope.endDate = $scope.grouping.endDate ? new Date($scope.grouping.endDate) : new Date()
$scope.nda = $scope.grouping.nda == "No" ? false : true;
$scope.grouping.invitation = $scope.grouping.invitateOnly == true ? 'true' : 'false';
$scope.canManageGrouping = UserService.memberships[$scope.grouping.id].role.canManageGrouping;
$scope.canCreateGroup = UserService.memberships[$scope.grouping.id].role.canCreateGroup;
})
EngageService.getProjects().then(function(res) {
$scope.projects = res.data.rows;
})
}
and here is the code for testing the promise
describe('Tests for WorkspaceEdit', functio
var getGroupingPromise;
beforeEach(function() {
module('iq4App');
return inject(function($injector) {
this.EngageService = {
getGrouping: function(gid) {
getGroupingPromise = $q.defer();
console.log(getGroupingPromise.promise)
return getGroupingPromise.promise;
}
};
this.scope = this.rootScope.$new();
this.scope.$digest();
}); '$upload': this.upload,
'ResourceService': this.ResourceService,
'MatchService': this.MatchService
});
this.scope.$digest();
});
});
});
it('should set stuff on init', function() {
var fakeData = {
data: {
parent: {
id: 3
}
}
}
this.scope.init();
this.scope.$digest();
getGroupingPromise.resolve(fakeData.data)
expect(this.scope.grouping).toBe(fakeData.data);
})
});
I am trying to resolve some fake data to $scope.grouping but the data is resolving as undefined. I know the promise is being created from my console.log in the getGrouping method in this.EngageService. I've stripped out a lot of code that I didn't think was relevant but let me know if you want more detail.
Thank you!
You need to resolve the promise with the fake data:
getGroupingPromise = $q.defer();
getGroupingPromise.resolve(fakeData.data);
return getGroupingPromise.promise;
The digest in my test had to go after the resolve and not before.

Returning a value in AngularJS

I wrote an angular service which querys a db and should return the Categories:
(function() {
'use strict';
angular.module('budget')
.service('CategoriesService', ['$q', CategoriesService]);
function CategoriesService($q) {
var self = this;
self.loadCategories = loadCategories;
self.saveCategorie = saveCategorie;
self.datastore = require('nedb');
self.db = new self.datastore({ filename: 'datastore/default.db', autoload : true});
function saveCategorie (categorie_name) {
var entry = {name: categorie_name,
type: 'categorie'}
self.db.insert(entry);
};
function loadCategories () {
self.db.find({type: 'categorie'}, function (err, docs) {
var categories = docs;
return categories;
});
};
return {
loadCategories: self.loadCategories,
saveCategorie: self.saveCategorie
};
}
})();
When I console.log inside the function loadCategories() it returns me an array of 6 objects (the objects from the database) but outside of the function it just gives me undefined.
I am calling via the controller with CategoriesService.loadCategories()
So I think I might have to do something thas called promise but Iam not sure about that.
How can I get acctual data back from this service?
First of all you don't need to return anything from the service factory recipe, you just need to assign a method to the this variable.
At least, you need:
// service.js
self.loadCategories = function() {
var deferred = $q.defer();
db.find({type: 'categorie'}, function (err, docs) {
deferred.resolve(docs);
});
return deferred.promise;
};
// controller.js
service
.loadCategories()
.then(function(categories) {
$scope.categories = categories;
})
;
you need to return your promise first so just add one more return and you are good to go...
function loadCategories () {
// you need to return promise first and you can resolve your promise in your controller
return self.db.find({type: 'categorie'}, function (err, docs) {
var categories = docs;
return categories;
});
};

How to resolve my promise calls in my case

I was trying to reduce my promise objects in my service. I have something like
angular.module('myApp').service('testService', ['Employees','$q',
function(Employees, $q) {
var service = {};
var firstEmp;
var deferred = $q.defer();
Employees.query({
Id: 123
}, function(objects) {
firstEmp = objects[0];
deferred.resolve(objects);
})
service.getFirstEmployee = function() {
var deferredtwo = $q.defer();
// return deferredtwo.promise;
//How to solve the double promise defer in my case
//First promise is wait for the whole employees
//this one is to return first employee
deferred.promise.then(function(){
deferredtwo.resolve(firstEmp);
})
return deferredtwo.promise;
}
return service;
]);
Controller
testService.getFirstEmployee.then(function(firstEmployee){
console.log(firstEmployee) <---show first employee
})
I am not sure how to resolve the double promise objects. Can anyone help me? Thanks a lot!
If your ultimate objective is just to get the first employee, then you don't need all this "double promise" stuff at all. Just resolve one promise with the first employee:
angular.module('myApp').service('testService', ['Employees','$q',
function(Employees, $q) {
var pFirstEmployee = $q(function (resolve) {
Employees.query({ Id: 123 }, function(objects) {
resolve(objects[0]);
});
});
return {
getFirstEmployee: function() {
return pFirstEmployee;
}
};
}
]);
If you want two methods - one that returns a promise for all employees returned from the query, and another that returns just the first one from that set, just create a promise for all the employees and chain off of that:
angular.module('myApp').service('testService', ['Employees','$q',
function(Employees, $q) {
var pAllEmployees = $q(function (resolve) {
Employees.query({ Id: 123 }, resolve);
}),
pFirstEmployee = pAllEmployees.then(function (employees) {
return employees[0];
});
return {
getAllEmployees: function() {
return pAllEmployees;
},
getFirstEmployee: function() {
return pFirstEmployee;
}
};
}
]);
After the clarification I guess this is what you want:
angular.module('myApp').service('testService', ['Employees','$q',
function(Employees, $q) {
var service = {};
var deferred = $q.defer();
Employees.query({Id: 123}, function(objects) {
firstEmp = objects[0];
deferred.resolve(objects);
});
service.getFirstEmployee = function() {
return deferred.promise.then(function(employees){
return employees[0];
});
}
return service;
]);

Testing a loader in angular js

This is a test for a $resource with a loader
describe('Service: MultiCalculationsLoader', function(){
beforeEach(module('clientApp'));
var MultiCalculationLoader,
mockBackend,
calculation;
beforeEach(inject(function (_$httpBackend_, Calculation, _MultiCalculationLoader_) {
MultiCalculationLoader = _MultiCalculationLoader_;
mockBackend = _$httpBackend_;
calculation = Calculation;
}));
it('should load a list of calculation from a user', function(){
mockBackend.expectGET('/api/user/600/calculation').respond([{id:5}]);
var calculations;
var mockStateParams = {
userId: 600
};
var promise = new MultiCalculationLoader(mockStateParams);
promise.then(function(res){
calculations = res
});
expect(calculations).toBeUndefined();
mockBackend.flush();
expect(calculations).toEqual([{id:5}]);
});
});
When I run the test I get the following error:
Expected [ { id : 5 } ] to equal [ { id : 5 } ].
Error: Expected [ { id : 5 } ] to equal [ { id : 5 } ].
at null.<anonymous>
I don't get it. The two arrays are the same to me. Ideas anyone?
Update
Here's the implementation:
.factory('Calculation', function ($resource) {
return $resource('/api/user/:userId/calculation/:calcId', {'calcId': '#calcId'});
})
.factory('MultiCalculationLoader', function (Calculation, $q) {
return function ($stateParams) {
var delay = $q.defer();
Calculation.query( {userId: $stateParams.userId},function (calcs) {
delay.resolve(calcs);
}, function () {
delay.reject('Unable to fetch calculations');
});
return delay.promise;
};
})
The url you expect is different from the actual url. I guess you need something like this:
it('should load a list of calculation from a user', function(){
//remove the 's' from 'calculations'
mockBackend.expectGET('/api/user/600/calculation').respond([{id:5}]);
var calculations;
var promise = MultiCalculationLoader({userId:600}); //userId = 600
promise.then(function(res){
calculations = res
});
expect(calculations).toBeUndefined();
mockBackend.flush();
expect(calculations).toEqual([{id:5}]);
});
There is another problem that angular automatically adds 2 properties to the response:
http://plnkr.co/edit/gIHolGd85SLswzv5VZ1E?p=preview
It's kind of the same problem as: AngularJS + Jasmine: Comparing objects
This is really a problem with angular $resource when angular convert the response to resource objects. In order to verify responses from $resource, you could try angular.equals
expect(angular.equals(calculations,[{id:5},{id:6}])).toBe(true);
http://plnkr.co/edit/PrZhk2hkvER2XTBIW7yv?p=preview
You could also write a custom matcher:
beforeEach(function() {
jasmine.addMatchers({
toEqualData: function() {
return {
compare: function(actual, expected) {
return {
pass: angular.equals(actual, expected)
};
}
};
}
});
});
And use it:
expect(calculations).toEqualData([{id:5},{id:6}]);
http://plnkr.co/edit/vNfRmc6R1G69kg0DyjZf?p=preview
Jasmine equality selectors can be too specfic sometimes when you just wanna check for equaity.
I have never seen it with the toEqual() method when comparing objects or arrays, but def with the toBe() method.
Try to replace toEqual() with toMatch().
Also in the unit test I recommend using a constant value that you can pass in the response and the matchers/equal/toBe's.
describe('Service: MultiCalculationsLoader', function(){
beforeEach(module('clientApp'));
var MultiCalculationLoader,
mockBackend,
calculation,
VALUE = [{id:5}];
beforeEach(inject(function (_$httpBackend_, Calculation, _MultiCalculationLoader_) {
MultiCalculationLoader = _MultiCalculationLoader_;
mockBackend = _$httpBackend_;
calculation = Calculation;
}));
it('should load a list of calculation from a user', function(){
mockBackend.expectGET('/api/user/600/calculations').respond(VALUE);
var calculations;
var promise = MultiCalculationLoader();
promise.then(function(res){
calculations = res
});
expect(calculations).toBeUndefined();
mockBackend.flush();
expect(calculations).toEqual(VALUE);
});
});
Using this approach I think the .toEqual will actually work.
Our approach:
Before Block:
httpBackend.when('JSONP', PATH.url + 'products?callback=JSON_CALLBACK&category=123').respond(CATEGORIES[0]);
Test:
describe('Category Method', function () {
it('Should return the first category when the method category is called', function () {
var result = '';
service.category(123).then(function(response) {
result = response;
});
httpBackend.flush();
expect(result).toEqual(CATEGORIES[0]);
});
});
you can try change toEqual() with toEqualData()
expect(calculations).toEqualData([{id:5}]);

Accessing a variable initialized via a Factory which launches an async request

I've got a factory that sends a POST request to get some JSON key-value pairs:
.factory('Rest', ['$resource',
function($resource) {
// returns JSON key-value pairs, e.g. "{'foo', 'bar'}"
return $resource('rest/get', {}, {
get: {
method: 'POST'
}
});
}])
I've got another factory intended to be exposed to controllers in order to access a key-value pair given its key:
.factory('Pairs', ['Rest',
function(Rest) {
var pairs;
Rest.get(function(response) {
pairs = response;
});
return {
get: function(key) {
return pairs[key];
}
};
}])
The problem is that, when I call Pairs.get('foo'), the pairs object of the Pairs factory is not initialized yet (since Rest.get is asynchronous), and thus results in a TypeError: Cannot read property 'foo' of undefined:
.controller('AnyController', ['Pairs',
function (Pairs) {
console.log(Pairs.get('foo')); // error
}])
Any idea how to solve this?
As you stated in your question, Rest.get is asynchronous, so your Pairs.get has to be asynchronous too. You can implement it as the following:
.factory('Pairs', ['Rest', '$q',
function(Rest, $q) {
var pairs;
var deferredList = [];
Rest.get(function(response) {
pairs = response;
angular.forEach(deferredList, function(o) {
o.deferred.resolve(pairs[o.key]); // resolve saved defer object
});
deferredList = null; // we don't need the list anymore
});
return {
get: function(key) {
if (pairs) {
return $q.when(pairs[key]); // make immediate value as a promise
}
var deferred = $q.defer(); // create a deferred object which will be resolved later
deferredList.push({ // save both key and deferred object for later
key: key,
deferred: deferred
});
return deferred.promise;
}
};
}])
Then use it like this:
Pairs.get('foo').then(function(value) {
console.log(value);
});
You want to wrap your async function in a promise. Here's how I've done something similar.
Note: safeApply triggers the $digest cycle, if necessary, so that angular can react to any data changes it might be watching.
var safeApply = function (scope, fn) {
if (scope.$$phase || scope.$root.$$phase) {
fn();
} else {
scope.$apply(fn);
}
};
ret.getAll = function(type) {
var deferred = $q.defer();
var where = "apm_type = '" + type + "'";
query(type, where, function(err, response) {
var objs = [];
if (err) {
safeApply($rootScope, function() { deferred.reject(err);});
} else {
safeApply($rootScope, function() { deferred.resolve(response);});
}
});
return deferred.promise;
};

Categories

Resources