Angular Unit Testing Service with multiple service calls using $q.all - angularjs

Ok here is the scenario we are trying to unit test with Jasmine. We have a service defined similar to the service below:
(function () {
'use strict';
angular.module('mymodule')
.service('myservice', myservice);
myservice.$inject = ['$q', '$resource', 'progressService',
'myservice2', 'myservice3', 'ngDialog'];
function myservice($q, $resource, progressService,
myservice2, myservice3, ngDialog) {
var self = this;
self.dataListSvc2 = [];
self.dataListSvc3 = [];
self.dataFromResource = null;
self.myRoutine = myRoutine;
var myResource = $resource('/someurl/webapi/GetData');
//TRYING TO TEST THIS ROUTINE!!!
function myRoutine(param1, param2) {
return progressService.show($q.all([
myResource.get({ Param1: param1 }).$promise.then(function (response) {
self.dataFromResource = response;
}),
myRoutine2(param2),
myRoutine3(param2)
]));
}
function myRoutine2(param) {
return myservice2.getSomeData(param).then(function (response) {
var results = [];
self.dataListSvc2 = [];
response.forEach(function (item) {
item.AddField1 = null;
item.AddField2 = false;
results.push(item);
});
self.dataListSvc2 = results;
});
}
function myRoutine3(param) {
return myservice3.getSomeMoreData(param, [6])
.then(function (response) {
self.dataListSvc3 = response;
});
}
}
})();
I am trying to write a unit test for myservice.myRoutine() and not having any luck, I suspect it is due to the $q.all array of promises. Here is a test I settled on but it is not ideal and honestly I don't feel like it's testing anything of value. I have also tried "mocking" the three requests with $httpBackend with no luck as all three responses come back in an array with undefined values. I searched around SO and the web and I am only finding $q.all([]) unit tests referencing controllers but not services. If anyone has some input it would be much appreciated. Here is where I settled for the time being:
describe('My Service: ', function () {
var $httpBackend;
beforeEach(module('mymodule'));
beforeEach(inject(function ($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
}));
it('Can call myroutine from myservice', inject(function (myservice) {
//Arrange
var expectedVal1 = 1234;
var expectedVal2 = 1234;
spyOn(myservice, "myRoutine");
//Act
myservice.myRoutine(expectedVal1, expectedVal2);
//Assert
expect(myservice.myRoutine).toHaveBeenCalled();
}));
});

Use the $httpBackend.when('GET', '/someurl/webapi/GetData').respond(data); to fake the http requests. You should fake the myservice2.getSomeData method too and the myservice3.getSomeMoreData I guess those methods create http request too so you could fake them in the same manner.
expect(myservice.myRoutine).toHaveBeenCalled();
Is irrelevant as you called it manually ;-).
var fixture = {/*some data ... */};
it('Can call myroutine from myservice', inject(function (myservice) {
$httpBackend.when('GET', '/someurl/webapi/GetData').respond(fixture);
//Act
myservice.myRoutine(expectedVal1, expectedVal2);
$rootScope.$apply()
//Assert
expect(myservice.dataFromResource).toBe(fixture);
}));

Related

How to write test case for JSON getting form factory in AngularJS

I am trying to write the test cass for the factory which is returing a JSON response.
But I am getting the error:
Error: [$injector:unpr] http://errors.angularjs.org/1.4.1/$injector/unpr?p0=serviceProvider%20%3C-%20service
at Error (native)
Here is my code:
(function () {
angular.module('uspDeviceService',[]).factory('getDevice', GetDevice);
GetDevice.$inject = ['$http'];
function GetDevice($http) {
getDeviceList = function() {
return $http.get("static/test-json/devices/device-list.json");
}
return {
getDeviceList: getDeviceList
}
}
}());
Code for Test case:
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
angular.mock.inject(function ($injector) {
//Injecting $http dependencies
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
})
});
console.log('Injection Dependencies is done');
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device-list.json").respond("Response found!");
httpBackend.flush();
}))
})
});
I am new to Angular Unit testing, can anyone please help me, where I am going wrong..
Two things that jump out at me:
Your angular.module declaration is defining a module, not getting the module. I would encourage you to split that up so that it's a fair bit more clear what your intent is.
angular.module('uspDeviceService', []);
angular.module('uspDeviceService').factory('getDevice', GetDevice);
It likely works as-is, but clarity is important.
What is...service? It's not defined anywhere in your code, and Angular can't find it either, hence the error message. You may be looking to get getDevice instead. Also, name your test variable with respect to what it actually is, so you don't confuse yourself.
// defined above
var getDevice;
// while injecting
getDevice = $injector.get('getDevice');
Supposing that you have an angularjs controller myController defined in myModule. The controller do some action when the api call is success and shows a flash message when api returns success = false. The your controller code would be something like
angular.module('myModule')
.controller( 'myController', function ( $scope,flashService, Api ) {
Api.get_list().$promise.then(function(data){
if(data.success) {
$scope.data = data.response
}
else{
flashService.createFlash(data.message, "danger");
}
});
});
Now to test both success = true and success = false we
describe('myController', function(){
var $rootScope, $httpBackend, controller, flashService;
var apilink = 'http://apilink';
beforeEach(module('myModule'));
beforeEach(inject(function(_$httpBackend_,_$rootScope_, _$controller_, _flashService_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
flashService = _flashService_;
controller = _$controller_("myController", {$scope: $rootScope});
}));
it('init $scope.data when success = true', function(){
$httpBackend.whenGET(apilink)
.respond(
{
success: true,
response: {}
});
$httpBackend.flush();
expect($rootScope.data).toBeDefined();
});
it('show flash when api request failure', function(){
spyOn(flashService, 'createFlash');
$httpBackend.whenGET(apilink)
.respond(
{
success: false
});
$httpBackend.flush();
expect(flashService.createFlash).toHaveBeenCalled();
});
});
You are always going to mock the response because here we are testing the javascript code behaviour and we are not concerned with the Api. You can see when success the data is initialized and when success is false createFlash is called.
As far as test for factory is concerned you can do
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
inject(function ($injector) {
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
});
});
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device- list.json").respond("Response found!");
var result = getDevice.getDeviceList();
httpBackend.flush();
expect(result).toEqual('Response found!');
}));
});
});

How to mock a service using Jasmine to ignore any calls

I am writing a unit tests for below controller. I have two functions loadCountries and loadTimezones that I want to be called on page load. I want to test that countries are loaded on page load. In that particular test, I don't care whether timezones are loaded or not. So I mocked Timezones services. But looks like I've to return a value for timezones mock. I don't want to handle it explicitly. I was expecting when I do createSpyObj, any function calls that are not explicitly handled on the spy/mock will be dropped or will have no effect. If I don't chain returnValue, mock is calling real function. How do I fix this ?
'use strict';
angular.module('nileLeApp')
.controller('RegisterController', function ($scope, $translate, $timeout, vcRecaptchaService, Auth, Country, Timezone, RecaptchaService) {
$scope.success = null;
$scope.error = null;
$scope.doNotMatch = null;
$scope.errorUserExists = null;
$scope.registerAccount = {};
$timeout(function () {
angular.element('[ng-model="registerAccount.email"]').focus();
});
$scope.loadCountries = function () {
Country.getCountries()
.then(function (result) {
$scope.countries = result.data;
});
};
$scope.loadTimezones = function () {
Timezone.getTimezones()
.then(function (result) {
$scope.timezones = result.data;
});
};
$scope.loadCountries();
$scope.loadTimezones();
});
Below is the test I'm trying.
'use strict';
describe('Register Controllers Tests', function () {
describe('RegisterController', function () {
// actual implementations
var $scope;
var $q;
// mocks
var MockTimeout;
var MockTranslate;
var MockAuth;
var MockCountry;
var MockTimezone;
// local utility function
var createController;
beforeEach(inject(function ($injector) {
$q = $injector.get('$q');
$scope = $injector.get('$rootScope').$new();
MockTimeout = jasmine.createSpy('MockTimeout');
MockAuth = jasmine.createSpyObj('MockAuth', ['createAccount']);
MockCountry = jasmine.createSpyObj('MockCountry', ['getCountries']);
MockTimezone = jasmine.createSpyObj('MockTimezone', ['getTimezones']);
MockTranslate = jasmine.createSpyObj('MockTranslate', ['use']);
var locals = {
'$scope': $scope,
'$translate': MockTranslate,
'$timeout': MockTimeout,
'Auth': MockAuth,
'Country': MockCountry,
'Timezone': MockTimezone
};
createController = function () {
$injector.get('$controller')('RegisterController', locals);
};
}));
it('should load countries on page load', function () {
var mockCountryResponse = {data: [{
'countryId': 1,
'alpha2Code': "AF",
'countryName': "Afghanistan"
}]};
MockCountry.getCountries.and.returnValue($q.resolve(mockCountryResponse.data));
// Want to avoid explicitly specifying below line
MockTimezone.getTimezones.and.returnValue($q.resolve({}));
// given
createController();
$scope.$apply($scope.loadCountries);
expect($scope.countries).toEqual(mockCountryResponse);
});
});
It is not possible to get rid of and.returnValue here, because the controller chains a promise and expects the methods on the object that Timezone.getTimezones stub would return (and it returns none).
jasmine.createSpyObj only handles the calls to Timezone methods and not their return values, that's why and.returnValue is there.
It is totally fine to do
MockTimezone.getTimezones.and.returnValue($q.resolve({}));
in promise-based specs.

can't get promise result from angular service

I'm trying to mock a service I'm using and should return a promise, the mock service is being called but I can't get the result to my test.
service function to be tested:
function getDevices() {
console.log('getDevices');
return servicesUtils.doGetByDefaultTimeInterval('devices')
.then(getDevicesComplete);
function getDevicesComplete(data) {
console.log('getDevicesComplete');
var devices = data.data.result;
return devices;
}
}
My test is:
describe('devicesService tests', function () {
var devicesService;
var servicesUtils, $q, $rootScope;
beforeEach(function () {
servicesUtils = {};
module('app.core', function ($provide) {
servicesUtils = specHelper.mockServiceUtils($provide, $q, $rootScope);
});
inject(function (_devicesService_, _$q_, _$rootScope_) {
devicesService = _devicesService_;
$q = _$q_;
$rootScope = _$rootScope_.$new();
});
});
it('getting device list', function () {
console.log('getting device list');
devicesService.getDevices().then(function (result) {
console.log(result);
expect(result).toBeDefined();
});
});
});
Mock file:
function mockServiceUtils($provide, $q) {
var servicesUtils = {};
servicesUtils.doGetByDefaultTimeInterval = jasmine.createSpy().and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
$rootScope.$digest();
return deferred.promise;
});
$provide.value('servicesUtils', servicesUtils);
return servicesUtils;
}
Your code is way too complex.
Let's assume that you want to test a service devicesService that uses another service servicesUtils, having a method that returns a promise.
Let's assume devicesService's responsibility is to call servicesUtils and transform its result.
Here's how I would do it:
describe('devicesService', function() {
var devicesService, servicesUtils;
beforeEach(module('app.core'));
beforeEach(inject(function(_devicesService_, _servicesUtils_) {
devicesService = _devicesService_;
servicesUtils = _servicesUtils_;
}));
it('should get devices', inject(function($q, $rootScope) {
spyOn(servicesUtils, 'doGetByDefaultTimeInterval').and.returnValue($q.when('Remote call result'));
var actualResult;
devicesService.getDevices().then(function(result) {
actualResult = result;
});
$rootScope.$apply();
expect(actualResult).toEqual('The transformed Remote call result');
}));
});

unit test for service with a promise

I have a service that i wrote for a project i am currently working on and i am trying to write a unit test for it. Below is the code for the service
angular.module('services').factory('Authorization', ['$q', 'Refs', function($q, Refs) {
function isAuthorized() {
var deferred = $q.defer();
var authData = Refs.rootRef.getAuth();
var adminRef;
if(authData.google) {
adminRef = Refs.rootRef.child('admins').child(authData.uid);
} else {
adminRef = Refs.rootRef.child('admins').child(authData.auth.uid);
}
adminRef.on('value', function(adminSnap) {
deferred.resolve(adminSnap.val());
});
return deferred.promise;
}
return {
isAuthorized: isAuthorized
};
}]);
I have written a unit test for it but anytime i run the test i get this error message ' Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'
Below is the code for the unit test i wrote for the service:
'use strict';
describe('Authorization Service', function() {
var Refs, $q, Authorization, authData, scope, deferred;
beforeEach(angular.mock.module('Sidetime'));
beforeEach(inject(function(_Refs_, $rootScope, _$q_, _Authorization_) {
Refs = _Refs_;
$q = _$q_;
Authorization = _Authorization_;
}));
iit('return admin object', function(done) {
var result;
Authorization.isAuthorized = function() {
deferred = $q.defer();
authData = {google: 'uid:112222'};
if(authData.google) {
deferred.resolve(authData);
}
return deferred.promise;
};
Authorization.isAuthorized().then(function(result) {
console.log('result', result);
expect(result).toEqual(authData);
//done();
});
});
});
I am not sure I am writing the unit test properly. I will appreciate if someone could show be a better way of writing the unit test for this service. Thanks for your anticipated assistance.
Here is a working plunkr, with slight modifications as I don't have the code to all of your dependencies:
angular.module('services', []).factory('Authorization', ['$q', function($q) {
function isAuthorized() {
var deferred = $q.defer();
deferred.resolve({authData: {google: 'uid: 1122222'}});
return deferred.promise;
}
return {
isAuthorized: isAuthorized
};
}]);
describe('Authorization Service', function() {
var $q, Authorization, scope, deferred;
beforeEach(angular.mock.module('services'));
beforeEach(inject(function($rootScope, _$q_, _Authorization_) {
$q = _$q_;
scope = $rootScope.$new();
Authorization = _Authorization_;
Authorization.isAuthorized = function() {
deferred = $q.defer();
authData = {google: 'uid:112222'};
if(authData.google) {
deferred.resolve(authData);
}
return deferred.promise;
};
}));
it('return admin object', function(done) {
var result;
var promise = Authorization.isAuthorized();
promise.then(function(result) {
expect(result).toEqual(authData);
expect(result.google).toEqual('uid:112222e');
});
deferred.resolve(result);
scope.$digest();
});
});
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 250;
/**
Create the `HTMLReporter`, which Jasmine calls to provide results of each spec and each suite. The Reporter is responsible for presenting results to the user.
*/
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
/**
Delegate filtering of specs to the reporter. Allows for clicking on single suites or specs in the results to only run a subset of the suite.
*/
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
/**
Run all of the tests when the page finishes loading - and make sure to run any previous `onload` handler
### Test Results
Scroll down to see the results of all of these specs.
*/
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
//document.querySelector('.version').innerHTML = jasmineEnv.versionString();
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
http://plnkr.co/edit/DCrr6pVzF9D4OuayCZU7?p=preview
Take a look over at spies in jasmine to get a deeper introduction
I've taken Cognitroics plunkr and modified it in the way I usually write my test. Take a look at it here http://plnkr.co/edit/W5pP82CKj7tc6IO3Wj9S?p=preview
But basically what you should do is utilizing the awesomeness of spies in jasmine.
Here's how it looks like.
describe('My aweomse test suite', function(){
beforeEach(function(){
module('Awesome');
inject(function($injector){
this.MyService = $injector.get('MyService');
this.$q = $injector.get('$q');
this.$scope = $injector.get('$rootScope').$new();
spyOn(this.MyService, 'someMethod').andCallFake(function(){
var defer = this.$q.defer();
defer.resolve();
return defer.promise;
});
});
it('should do seomthing', function(){
// given
var result;
// when
this.MyServcie.someMethod().then(function(){
result = 'as promised';
});
this.$scope.$digest();
// then
expect(result).toEqual('as promised');
});
});
});

mock angular service/promise in a karma/jasmine test

I'm trying to write a karma/jasmine test and I would like some explanations about how mocks are working on a service which is returning a promise. I explain my situation :
I have a controller in which I do the following call :
mapService.getMapByUuid(mapUUID, isEditor).then(function(datas){
fillMapDatas(datas);
});
function fillMapDatas(datas){
if($scope.elements === undefined){
$scope.elements = [];
}
//Here while debugging my unit test, 'datas' contain the promise javascript object instead //of my real reponse.
debugger;
var allOfThem = _.union($scope.elements, datas.elements);
...
Here is how my service is :
(function () {
'use strict';
var serviceId = 'mapService';
angular.module('onmap.map-module.services').factory(serviceId, [
'$resource',
'appContext',
'restHello',
'restMap',
serviceFunc]);
function serviceFunc($resource, appContext, restHello, restMap) {
var Maps = $resource(appContext+restMap, {uuid: '#uuid', editor: '#editor'});
return{
getMapByUuid: function (uuid, modeEditor) {
var maps = Maps.get({'uuid' : uuid, 'editor': modeEditor});
return maps.$promise;
}
};
}
})();
And finally, here is my unit test :
describe('Map controller', function() {
var $scope, $rootScope, $httpBackend, $timeout, createController, MapService, $resource;
beforeEach(module('onmapApp'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('maps.ctrl', {
'$scope': $scope
});
};
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
var response = {"elements":[1,2,3]};
it('should allow user to get a map', function() {
var controller = createController();
$httpBackend.expect('GET', '/onmap/rest/map/MY-UUID?editor=true')
.respond({
"success": response
});
// hope to call /onmap/rest/map/MY-UUID?editor=true url and hope to have response as the fillMapDatas parameter
$scope.getMapByUUID('MY-UUID', true);
$httpBackend.flush();
});
});
What I really want to do is to have my response object ( {"elements:...}) as the datas parameter of the fillMapDatas function. I don't understand how to mock all the service things (service, promise, then)
So you want to test, if your service responses as expected? Then, this is something you would rather test on the service. Unit test promise based methods could look like this:
var mapService, $httpBackend, $q, $rootScope;
beforeEach(inject(function (_mapService_, _$httpBackend_, _$q_, _$rootScope_) {
mapService = mapService;
$httpBackend = _$httpBackend_;
$q = _$q_;
$rootScope = _$rootScope_;
// expect the actual request
$httpBackend.expect('GET', '/onmap/rest/map/uuid?editor=true');
// react on that request
$httpBackend.whenGET('/onmap/rest/map/uuid?editor=true').respond({
success: {
elements: [1, 2, 3]
}
});
}));
As you can see, you don't need to use $injector, since you can inject your needed services directly. If you wanna use the correct service names throughout your tests, you can inject them with prefixed and suffixed "_", inject() is smart enough to recognise which service you mean. We also setup the $httpBackend mock for each it() spec. And we set up $q and $rootScope for later processing.
Here's how you could test that your service method returns a promise:
it('should return a promise', function () {
expect(mapService.getMapUuid('uuid', true).then).toBeDefined();
});
Since a promise always has a .then() method, we can check for this property to see if it's a promise or not (of course, other objects could have this method too).
Next you can test of the promise you get resolves with the proper value. You can do that setting up a deferred that you explicitly resolve.
it('should resolve with [something]', function () {
var data;
// set up a deferred
var deferred = $q.defer();
// get promise reference
var promise = deferred.promise;
// set up promise resolve callback
promise.then(function (response) {
data = response.success;
});
mapService.getMapUuid('uuid', true).then(function(response) {
// resolve our deferred with the response when it returns
deferred.resolve(response);
});
// force `$digest` to resolve/reject deferreds
$rootScope.$digest();
// make your actual test
expect(data).toEqual([something]);
});
Hope this helps!

Resources