I have written some service in angular. Check this PLUNKER.
Injecting CommonService, $rootRouter, ModalService in RouteService.
I am stuck with unit testing these services. You can see sample spec file at PLUNKER.
EDIT: Whatever test I have at plunker are not working as expected. Am not sure what I am doing wrong.
How to test goTo and getActivePage methods in RouteService?
How to test getProperty and setProperty methods in CommonService?
Here is code.
First service is RouteService
'use strict';
angular.module('mysampleapp')
.service('RouteService',
function(CommonService, $rootRouter, ModalService) {
console.log('RRRRRRRRRRRRRRRRRRRRRRRRRRRoute');
return {
goTo: goTo,
getActivePage: getActivePage
};
function goTo(page) {
var valid = CommonService.getProperty('isValidationSuccess');
switch (page) {
case 'AboutUs':
if (valid) {
CommonService.setProperty('activeMenu', page);
$rootRouter.navigate([page]);
} else {
ModalService.openModal('Analysis Error', 'Complete Application Group configuration prior to running analysis.', 'Error');
}
break;
default:
CommonService.setProperty('activeMenu', page);
$rootRouter.navigate([page]);
break;
}
}
function getActivePage() {
return CommonService.getProperty('activeMenu');
}
});
Another is CommonService
'use strict';
angular.module('mysampleapp')
.service('CommonService',
function() {
var obj = {
/* All page validation check before perform analysis */
isValidationSuccess: false,
/* Highlight the menu */
activeMenu: 'HomeMenu'
};
function setProperty(key, value) {
obj[key] = value;
}
function getProperty(key) {
return obj[key];
}
function getAllProperties() {
return obj;
}
return {
setProperty: setProperty,
getProperty: getProperty,
getAllProperties: getAllProperties
};
}
);
In your plunker you forgot to create the mysampleapp module before adding services to it:
angular.module('mysampleapp', []);
The test for setters and getters of CommonService should be pretty simple:
describe('CommonService', function () {
var commonService;
beforeEach(module('mysampleapp'));
beforeEach(inject(function (_CommonService_) {
commonService = _CommonService_;
}));
it('should set and get property', function () {
commonService.setProperty('isValidationSuccess', 'Perform');
expect(commonService.getProperty('isValidationSuccess')).toBe('Perform');
});
});
Unit tests for services in most cases should be islolated from other services. If you going to testing CommonService you must mock other services, such as CommonService and etc. Main reason that you do not have to worry how to run for another service, because in this test you expecting that other services will work correctly.
describe('RouteService', function () {
'use strict';
var RouteService,
ModalService,
CommonService,
mockedValue,
$rootRouter;
beforeEach(module('mysampleapp'));
beforeEach(inject(function (_RouteService_, _ModalService_, _CommonService_, _$rootRouter_) {
RouteService = _RouteService_;
ModalService = _ModalService_;
CommonService = _CommonService_;
$rootRouter = _$rootRouter_;
$rootRouter.navigate = jasmine.createSpy();
ModalService.openModal = jasmine.createSpy(); //sometimes open modal return promise, and you should check it to
CommonService.getProperty = jasmine.createSpy().and.callFake(function () {
return mockedValue;
});
CommonService.setProperty = jasmine.createSpy().and.callFake(function () {
return mockedValue;
});
}));
it('should exist', function () {
expect(RouteService).toBeDefined();
});
it('should get active page', function () {
RouteService.getActivePage();
expect(CommonService.getProperty).toHaveBeenCalled(); //this test make sens only for make you coverage 100%, in you case i mean
});
describe('goTo method', function () {
it('should check if it is valid page', function () {
RouteService.goTo();
expect(CommonService.getProperty).toHaveBeenCalled();
});
it('should set property if page is "about as" and if it is valid page, and should navigate to this page', function () {
mockedValue = true;
var page = 'AboutUs';
RouteService.goTo(page);
expect(CommonService.setProperty).toHaveBeenCalledWith('activeMenu', page);
expect($rootRouter.navigate).toHaveBeenCalledWith([page]);
expect(ModalService.openModal).not.toHaveBeenCalled();
});
it('should open modal with error if "about as" is not valid page', function () {
var isValid = mockedValue = false;
var page = 'AboutUs';
RouteService.goTo(page);
expect(ModalService.openModal).toHaveBeenCalled();
expect(CommonService.setProperty).not.toHaveBeenCalled();
expect($rootRouter.navigate).not.toHaveBeenCalled();
});
it('should set property and navigate to page', function () {
var page = 'Test Page';
RouteService.goTo(page);
expect(CommonService.setProperty).toHaveBeenCalledWith('activeMenu', page);
expect($rootRouter.navigate).toHaveBeenCalledWith([page]);
expect(ModalService.openModal).not.toHaveBeenCalled();
});
});
});
Related
Hi I have a Angular service that uses another service that loads data from the local storage on init.
angular
.module('app')
.factory('localStorage', function ($window)
{
if (!$window.localStorage)
{
// throw Error
}
return $window.localStorage;
});
angular
.module('app')
.factory('session', function (localStorage)
{
var container = JSON.parse(localStorage.getItem('sessionContainer'));
return {
getUser: getUser
};
});
Now i want to test the session service.
describe('SessionService', function ()
{
var service;
var localStorageMock;
// Load the module.
beforeEach(module('appRegistration'));
// Create mocks.
beforeEach(function ()
{
logMock = {};
localStorageMock = jasmine.createSpyObj('localStorageServiceMockSpy', ['setItem', 'getItem']);
localStorageMock.getItem.and.returnValue('{}');
module(function ($provide)
{
$provide.value('localStorage', localStorageMock);
});
inject(function (_session_)
{
service = _session_;
});
});
it('should call `getItem` on the `localStorageService` service', function ()
{
expect(localStorageMock.getItem).toHaveBeenCalledWith('sessionContainer');
});
describe('getUser method', function ()
{
it('should return an empty object when the user is not set', function ()
{
var result = service.getUser();
expect(result).toEqual({});
});
it('should return the user data', function ()
{
// localStorageMock.getItem.and.returnValue('{"user":{"some":"data"}}');
var result = service.getUser();
expect(result).toEqual({some: 'user data'});
});
});
});
As you can see in the should return the user data section.
I need a way to update the container so getUser returns the expected data.
I tried to update the getItem spy, but this does not work. The localStorageMock is already injected in the session service when i want to change the spy.
Any help?
The most simple way is to have a variable with mocked value that is common for both function scopes:
var getItemValue;
beforeEach({
localStorage: {
getItem: jasmine.createSpy().and.callFake(function () {
return getItemValue;
}),
setItem: jasmine.createSpy()
}
});
...
it('should return the user data', function ()
{
getItemValue = '{"user":{"some":"data"}}';
inject(function (_session_) {
service = _session_;
});
var result = service.getUser();
expect(result).toEqual({some: 'user data'});
});
Notice that inject should be moved from beforeEach to it for all specs (the specs that don't involve getItemValue may use shorter syntax, it('...', inject(function (session) { ... }))).
This reveals the flaw in service design that makes it test-unfriendly.
The solution is to make container lazily evaluated, so there is time to mock it after the app was bootstrapped with inject:
.factory('session', function (localStorage)
{
var containerCache;
function getUser() {
...
return this.container;
}
return {
get container() {
return (containerCache === undefined)
? (containerCache = JSON.parse(localStorage.getItem('sessionContainer')))
: containerCache;
},
getUser: getUser
};
});
Additionally, this makes possible to test session.container as well. In this case localStorageMock.getItem spy value may be redefined whenever needed.
Here is my service
angular.module('starter.services').factory('PaypalService', function ($q, $ionicPlatform, shopSettings, $filter, $timeout) {
var init_defer;
var service = {
initPaymentUI: initPaymentUI,
createPayment: createPayment,
configuration: configuration,
onPayPalMobileInit: onPayPalMobileInit,
makePayment: makePayment
};
return service;
function initPaymentUI() {
init_defer = $q.defer();
$ionicPlatform.ready().then(function () {
var clientIDs = {
"PayPalEnvironmentProduction": shopSettings.payPalProductionId,
"PayPalEnvironmentSandbox": shopSettings.payPalSandboxId
};
PayPalMobile.init(clientIDs, onPayPalMobileInit);
});
return init_defer.promise;
}
function createPayment(total, name) {
// "Sale == > immediate payment
// "Auth" for payment authorization only, to be captured separately at a later time.
// "Order" for taking an order, with authorization and capture to be done separately at a later time.
var payment = new PayPalPayment("" + total, "USD", "" + name, "Sale");
return payment;
}
function configuration() {
// for more options see `paypal-mobile-js-helper.js`
var config = new PayPalConfiguration({merchantName: shopSettings.payPalShopName});
return config;
}
function onPayPalMobileInit() {
$ionicPlatform.ready().then(function () {
// must be called
// use PayPalEnvironmentNoNetwork mode to get look and feel of the flow
PayPalMobile.prepareToRender(shopSettings.payPalEnv, configuration(), function () {
$timeout(function () {
init_defer.resolve();
});
});
});
}
function makePayment(total, name) {
var defer = $q.defer();
total = $filter('number')(total, 2);
$ionicPlatform.ready().then(function () {
PayPalMobile.renderSinglePaymentUI(createPayment(total, name), function (result) {
$timeout(function () {
defer.resolve(result);
});
}, function (error) {
$timeout(function () {
defer.reject(error);
});
});
});
return defer.promise;
}
})
and my settings
.constant('shopSettings',{
payPalSandboxId : 'id',
payPalProductionId : 'id',
payPalEnv: 'PayPalEnvironmentSandbox',
payPalShopName : 'app_name'
});
All I need is to make standard payment for user, so some user can log in in personal account, pay for some stuff and money will appear in my account.By the way everything works fine if I log in with sandbox test accounts, but not with real one's.
I have an Angular controller, which appeared to be working fine. I can console log the user variable inside of the service call, and it contains the correct data. However in my test, I can console log the controller and verify the user object is there, but it is empty. It really seems like initialize is trying to store the variable after the local scope is destroyed, but it is very strange as I have another controller & test written in the exact same way working fine.
I have been iterating over this for two days, so if anyone has any leads, I would be most grateful.
function DetailAccountController (accountsService) {
'use strict';
var user = {};
initialize();
return {
user: user
};
/**
* Initialize the controller,
* & fetch detail for a single user.
*/
function initialize () {
// If the service is available, then fetch the user
accountsService && accountsService.getById('').then(function (res) {
user = res;
});
}
}
and a jasmine test:
describe('DetailAccountController', function () {
var ctrl = require('./detail-account-controller'),
data = [{
"email": "fakeUser0#gmail.com",
"voornaam": "Mr Fake0",
"tussenvoegsel": "van0",
"achternaam": "User0",
"straat": "Mt Lincolnweg0",
"huisnr": 0,
"huisnr_toev": 0,
"postcode": "0LW",
"telefoonr": "0200000000",
"mobielnr": "0680000000",
"plaats": "Amsterdam",
"id": "00000000"
}],
accountsServiceMock,
$rootScope,
$q;
beforeEach(inject(function (_$q_, _$rootScope_) {
$q = _$q_;
$rootScope = _$rootScope_;
accountsServiceMock = {
getById: function () {}
};
}));
it('should call the getById method at least once', function () {
spyOn(accountsServiceMock, 'getById').and.returnValue($q.defer().promise);
ctrl.call({}, accountsServiceMock);
expect(accountsServiceMock.getById.calls.any()).toBe(true);
expect(accountsServiceMock.getById.calls.count()).toBe(1);
});
it('should populate user data in the model', function () {
var deferred = $q.defer();
deferred.resolve(data);
spyOn(accountsServiceMock, 'getById').and.returnValue(deferred.promise);
var vm = ctrl.call({}, accountsServiceMock);
$rootScope.$apply();
expect(vm.user).toEqual(data);
});
});
Updated solution for the curious
function DetailAccountController (accountsService) {
'use strict';
var self = this;
self.user = null;
initialize();
return self;
/**
* Initialize the controller,
* & fetch detail for a single user.
*/
function initialize () {
accountsService && accountsService.getById('').then(function (res) {
self.user = res;
});
}
}
user = res affects local variable and has nothing to do with returned object.
It has to be either
accountsService && accountsService.getById('').then(function (res) {
angular.extend(user, res);
});
or
var obj = {
user: {}
};
initialize();
return obj;
function initialize () {
accountsService && accountsService.getById('').then(function (res) {
obj.user = res;
});
}
I have little Angular Service to store and retrieve data. How do I write Jasmine test spec for testing this service?
angular.module("myServices").factory('dataStore', [
function() {
var DATASTORE;
DATASTORE = {};
return {
get: function(id) {
if (DATASTORE[id] != null) {
return DATASTORE[id];
} else {
return null;
}
},
put: function(id, data) {
return DATASTORE[id] = data;
}
};
}
]);
The below spec doesn't working for me:
"use strict";
describe("Service: dataStore", function() {
var store;
store = null;
beforeEach(function() {
module("myServices").inject([
'dataStore', function(dataStore) {
return store = dataStore;
}
]);
});
it("should return null", function() {
expect(store.get('some')).toBe(null);
});
});
First of all you should load your module within a beforEach block. After that you may use the inject function - angular and jasmin will do the rest for you.
"use strict";
describe("Service: dataStore", function() {
var store;
beforeEach(module('myServices'));
beforeEach(inject(function(dataStore){
store = dataStore;
}));
it("should return null", function() {
expect(store.get('some')).toBe(null);
});
});
Reading this Testing Angular Services Documentation should get you started
As for your problem, inject the myServices module in the beforeEach block and the dataStore service in the it block.
beforeEach(module('myServices'));
it("should return null",
inject(function(dataStore) {
expect(dataStore.get('some')).toBe(null);
}));
I have a unit test for an Angular service in which I test that a cache $cacheFactory is cleared after a call has been made for a save() method that does an http post to the backend. In 1.0.7 this test passed in Karma and Jasmine Specrunner.html, but after migrating to Angular 1.2.0 it fails. I have not changed any code in the service or in the spec file. The cache is cleared in production when I check it manually. Any ideas?
EDIT: Plunk of the error in action: http://plnkr.co/edit/1INhdM
The error message is:
Field service save() should clear field array from cache.
Expected 2 to be 1.
Error: Expected 2 to be 1.
at new jasmine.ExpectationResult (http://localhost:1234/js/test/lib/jasmine/jasmine.js:114:32)
at null.toBe (http://localhost:1234/js/test/lib/jasmine/jasmine.js:1235:29)
at http://localhost:1234/js/test/spec/field-serviceSpec.js:121:25
at wrappedCallback (http://localhost:1234/js/angular-1.2.0.js:10549:81)
at http://localhost:1234/js/angular-1.2.0.js:10635:26
at Scope.$eval (http://localhost:1234/js/angular-1.2.0.js:11528:28)
at Scope.$digest (http://localhost:1234/js/angular-1.2.0.js:11373:31)
at Scope.$delegate.__proto__.$digest (<anonymous>:844:31)
at Scope.$apply (http://localhost:1234/js/angular-1.2.0.js:11634:24)
at Scope.$delegate.__proto__.$apply (<anonymous>:855:30)
The service I am testing:
angular.module('services.field', [])
.factory('Field', ['$http', '$cacheFactory', function ($http, $cacheFactory) {
var fieldListCache = $cacheFactory('fieldList');
var Field = function (data) {
angular.extend(this, data);
};
// add static method to retrieve all fields
Field.query = function () {
return $http.get('api/ParamSetting', {cache:fieldListCache}).then(function (response) {
var fields = [];
angular.forEach(response.data, function (data) {
fields.push(new Field(data));
});
return fields;
});
};
// add static method to retrieve Field by id
Field.get = function (id) {
return $http.get('api/ParamSetting/' + id).then(function (response) {
return new Field(response.data);
});
};
// add static method to save Field
Field.prototype.save = function () {
fieldListCache.removeAll();
var field = this;
return $http.post('api/ParamSetting', field ).then(function (response) {
field.Id = response.data.d;
return field;
});
};
return Field;
}]);
The unit test that is failing:
'use strict';
describe('Field service', function() {
var Field, $httpBackend;
// load the service module
beforeEach(module('services.field'));
// instantiate service
beforeEach(inject(function(_Field_, _$httpBackend_) {
Field = _Field_;
$httpBackend = _$httpBackend_;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe("save()", function() {
it('should clear field array from cache', function () {
var firstMockData = [{ Alias: 'Alias 1' }, { Alias: 'Alias 2' }];
var secondMockData = [{ Alias: 'Alias 3' }];
var newField = new Field({});
var counter = 0;
$httpBackend.when('GET', 'api/ParamSetting').respond(function () {
// return firstMockData on first request and secondMockdata on subsequent requests
if (counter === 0) {
counter++;
return [200, firstMockData, {}];
} else {
return [200, secondMockData, {}];
}
});
$httpBackend.when('POST', 'api/ParamSetting').respond({});
// query fields
Field.query();
// save new field
newField.save();
// query fields again
Field.query().then(function (data) {
expect(data.length).toBe(secondMockData.length);
expect(data[0].Alias).toBe(secondMockData[0].Alias);
});
$httpBackend.flush();
});
});
});
The answer is that I am erroneously expecting asynchronyous requests to return responses in a particular order, and that my requests are cached until I call $httpBackend.flush() which would lead to .query() only being called once. To make it work, one can make the calls synchronous by adding another flush after the first query() call: http://plnkr.co/edit/MzuplQnkQunDyvy6vCvy?p=preview
The following code will allow you to mock out the $cacheFactory in your unit tests. The $provide service will allow the service dependency injection to use your $cacheFactory instead of the default $cacheFactory.
var cache, $cacheFactory; //used in your its
beforeEach(function(){
module(function ($provide) {
$cacheFactory = function(){};
$cacheFactory.get = function(){};
cache = {
removeAll: function (){}
};
spyOn(cache, 'removeAll');
spyOn($cacheFactory, 'get').and.returnValue(cache);
$provide.value('$cacheFactory', $cacheFactory);
});
});
describe('yourFunction', function(){
it('calls cache.remove()', function(){
yourService.yourFunction();
expect(cache.remove).toHaveBeenCalled();
});
});