Testing a loader in angular js - javascript

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}]);

Related

Passing gathered value in API call from first method to second in same object

I have a object which contains two methods. The first one is calling API and storing response in variable.
In second method I execute first one using this.nameOfFirstMethod() and then I want to do some calculation basing on numbers which I collected in API call in first method.
To make it more clear take a look at code, start reading at second method:
this.currencyConverter = {
getRatio: function(selectedCurrency) {
var selectedCurrency = selectedCurrency;
$http({
url: 'http://api.fixer.io/latest?base='+selectedCurrency+'&symbols=PLN,CHF,EUR,USD,GBP',
method: 'GET'
})
.then(function(response) {
var currentCurrency = {
toPLN: response.data.rates.PLN,
toCHF: response.data.rates.CHF,
toEUR: response.data.rates.EUR,
toUSD: response.data.rates.USD,
toUSD: response.data.rates.GBP
};
console.log("Succesful store currentCurrency");
return currentCurrency;
}, function(response) {
console.log("Problem occure while downloading money current currency!");
console.log(response.data);
});
},
convertMoney: function(selectedCurrency,priceField) {
var priceField = priceField;
var selectedCurrency = selectedCurrency;
console.log('selectedCurrency in service: '+selectedCurrency);
console.log('priceField in service: '+priceField);
this.getRatio(selectedCurrency);
console.log(currentCurrency);
/*
var converted = {
PLN: function() { return priceField * $rootScope.currentCurrency.toPLN; },
USD: function() { return priceField * $rootScope.currentCurrency.toUSD; },
EUR: function() { return priceField * $rootScope.currentCurrency.toEUR; },
CHF: function() { return priceField * $rootScope.currentCurrency.toCHF; },
GBP: function() { return priceField * $rootScope.currentCurrency.toGBP; }
};
*/
}
}
Here is GIST of same code if someone doesn't like StackOverflow styling:
https://gist.github.com/anonymous/e03de4de1af407bf70f4038acd77c961
Please open this gist because I will now explain basing on specific line.
So in the line 30 I execute first method.
In line 9 I'm storing retrieved data in variable and in line 17 returning this data (in order to use it in second method).
Finally I want to console.log this in second object in line 32 (for now only console.log I will do my maths later on).
It doesn't work with this return, the line with console.log in second method cause following error:
ReferenceError: currentCurrency is not defined
you don't assign the return value of getRatio to a variable
it should be
currentCurrency = this.getRatio(selectedCurrency);
And you should work with promises correctly.
So change it to something like this (not tested)
this.currencyConverter = {
getRatio: function(selectedCurrency) {
var selectedCurrency = selectedCurrency;
return $http({
url: 'http://api.fixer.io/latest?base='+selectedCurrency+'&symbols=PLN,CHF,EUR,USD,GBP',
method: 'GET'
})
.then(function(response) {
var currentCurrency = {
toPLN: response.data.rates.PLN,
toCHF: response.data.rates.CHF,
toEUR: response.data.rates.EUR,
toUSD: response.data.rates.USD,
toUSD: response.data.rates.GBP
};
console.log("Succesful store currentCurrency");
return currentCurrency;
}, function(response) {
console.log("Problem occure while downloading money current currency!");
console.log(response.data);
});
},
convertMoney: function(selectedCurrency,priceField) {
var priceField = priceField;
var selectedCurrency = selectedCurrency;
console.log('selectedCurrency in service: '+selectedCurrency);
console.log('priceField in service: '+priceField);
var currentCurrency = this.getRatio(selectedCurrency);
currentCurrency.then(res => console.log(res));
//console.log(currentCurrency);
}
}

Unit testing for function

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
});
});

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 can I get only the object with my arrays returned from a function with a promise?

I want the data in res passed to my notes variable. But it's returning a bigger nested object. Why is it happening?
If I inspect in the console the value of cleanArrayOfNotes I get the object that I want, but once its assigned to notes it becomes a quite bigger object. I understand it's part of the nature of the Promises, which at the moment I still trying to understand. Any help?
notes_service.js
var notesService = {notesObjectInService: [], newNote: null};
notesService.getAll = function() {
return $http.get('/notes.json').success(function(data){
//console.log(data)
angular.copy(data, notesService.notesObjectInService);
//console.log(notesService)
})
};
navCtrl.js
var notes = notesService.getAll().then(function(res){
var cleanArrayOfNotes = res.data;
//navCtrl line12
console.log(cleanArrayOfNotes);
return cleanArrayOfNotes;
});
//navCtrl line16
console.log(notes);
This should work for you:
notes_service.js
app.factory ('NoteService', function($http) {
return {
getAll: function() {
return $http.get('/notes.json').then(function(response) {
return response.data;
});
}
}
});
navCtrl.js
NotesService.getAll().then(function(res){
$scope.cleanArrayOfNotes = res.data;
});
Or, if you want to return the result rather than the promise, you can:
notes_service.js
app.factory ('NoteService', function($http) {
var notes = [];
var promise = $http.get('/notes.json').then(function(response) {
angular.copy(response.data, notes);
return notes;
});
return {
getAll: function() {
return notes;
},
promise: promise
}
});
navCtrl.js
// get array of notes
scope.cleanArrayOfNotes = NotesService.getAll();
// or use promise
NoteService.promise.then(function(response) {
scope.cleanArrayOfNotes = response.data;
});

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