How can I inject $window when testing a custom AngularJS provider? - angularjs

I need to unit test a custom provider overriding the $windowProvider.
provider.js
angular
.module('customProvider', [])
.provider('cprovider', [
'$windowProvider',
function ($windowProvider) {
var $window = $windowProvider.$get();
var platform = function () {
// some code there use $window
};
this.platform = platform;
this.$get = function () {
return {
platform: platform
};
};
}
]);
cprovider.spec.js
describe('cprovider', function () {
var cprovider, mockWindow;
describe("xxxx", function () {
beforeEach(function () {
mockWindow = {navigator: {userAgent: 'xxxx'}};
module('customProvider', function ($provide) {
$provide.value('$window', 'mockWindow');
});
inject(function (_cprovider_) {
cprovider = _cprovider_;
});
});
it('should something', function () {
// Arrange and Act in beforeEach.
// Assert. WON'T WORK
expect(cprovider.platform()).toBe('xxx');
});
});
});
Can't mock properly the $windowProvider.
Anyone knows how can I do that?

You can spyOn $window:
beforeEach(function () {
angular.mock.module('customProvider');
inject(function (_$window_, _cprovider_) {
cprovider = _cprovider_;
$window = _$window_;
spyOn($window, 'alert');
});
});
Complete fiddle here

Related

How to Spy on a method of Service from controller?

I am writing the UT for controller and while trying to implement for commandRouter.execute method(please refer 2nd spec) , I am getting error message : can not read property 'execute' of undefined.
Can someone let me know what am i doing wrong here and whats the correct way to spy on a method from controller. ?
module.controller('DcsPlus.AP.OmsControl.omsMasterRecipeDialogPopUpController', omsMasterRecipeDialogPopUpController);
omsMasterRecipeDialogPopUpController.$inject = [
'DcsPlus.Frame.Logic.commandRouter'
];
function omsMasterRecipeDialogPopUpController(commandRouter) {
var vm = this;
vm.execute = function(command) {
commandRouter.execute(command);
};
}
controller.spec.js
describe('omsMasterRecipeDialogPopUpController', function () {
var omsMasterRecipeDialogPopUpControllerTest;
var commandRouterMock;
var $scope;
beforeEach(function () {
registerMockServices();
prepareCommandRouterMock();
});
describe('execute', function () {
it('1. Should check if execute method is defined', function() {
expect(omsMasterRecipeDialogPopUpControllerTest.execute).toBeDefined();
});
it('2. Should check if execute method of commandRouter is called', function() {
omsMasterRecipeDialogPopUpControllerTest.execute();
expect(commandRouterMock.execute).toHaveBeenCalled();
});
});
function prepareCommandRouterMock() {
commandRouterMock = {
execute: function() {
}
};
}
/*beforeEach(function () {
commandRouterMock = jasmine.createSpyObj('DcsPlus.Frame.Logic.commandRouter', ['execute']);
});*/
function registerMockServices() {
angular.mock.module('DcsPlus.AP.OmsControl', function ($provide) {
$provide.value('DcsPlus.Frame.Logic.commandRouter', commandRouterMock);
});
angular.mock.inject(['$controller', '$rootScope', 'dialogService',
function ($controller, $rootScope, dialogService) {
$scope = $rootScope.$new();
spyOn(commandRouterMock, 'execute').and.callThrough();
// Init the controller, passing our spy service instance
omsMasterRecipeDialogPopUpControllerTest = $controller('DcsPlus.AP.OmsControl.omsMasterRecipeDialogPopUpController', {
$scope: $scope
});
}]);
}
});
In the beginning you create commandRouterMock but never assign it to anything.
Try this:
beforeEach(function () {
registerMockServices();
commandRouterMock = prepareCommandRouterMock();
});
function prepareCommandRouterMock() {
return {
execute: function() {
}
};
}

Angular unit testing Controller with service

I'm trying to write a unit test using karma and jasmine for an Angular controller that depends on a service
storesController.js
(function () {
var app = angular.module('storesController', ['storesService']);
app.controller('StoresListController', function ($scope, StoresService) {
$scope.getStores = function () {
StoresService.getStores().then(function (data) {
$scope.stores = data.data;
});
};
$scope.getStores();
$scope.deleteStore = function (id) {
StoresService.deleteStore(id).then(function () {
$scope.getStores();
});
};
});
storesService.js
(function () {
var app = angular.module('storesService', []);
app.factory('StoresService', ['$http', function ($http) {
var stores = [];
stores.getStores = function () {
return $http.get(/api/getStores');
};
stores.deleteStore = function (storeID) {
return $http.delete(/api/deleteStore/'+storeID);
};
return stores;
}]);
})();
And the test,
controllers.spec.js
describe('StoresController', function () {
beforeEach(module('storesController'));
var scope;
var storesServiceMock;
var controller;
beforeEach(inject(function ($controller, $rootScope) {
storesServiceMock = {
getStores: function() {
},
deleteStores: function() {
}
};
spyOn(storesServiceMock, 'getStores').and.returnValue({name : 'TestName', country : 'TestCountry'})
scope = $rootScope.$new();
controller = $controller('StoresListController', {
$scope: scope, StoresService: storesServiceMock
});
}));
it('scope.stores should be defined', function () {
expect(scope.stores).toBeDefined;
});
});
And I'm getting
TypeError: StoresService.getStores(...).then is not a function at n.$scope.getStores
I've also tried width httpBackend but I'm not be able to make it work, any clue about what I'm doing wrong?
Have the spy return a promise.
With ES2015:
spyOn(storesServiceMock, 'getStores').and.returnValue(Promise.resolve({name : 'TestName', country : 'TestCountry'}));
With $q:
spyOn(storesServiceMock, 'getStores').and.callFake(function() {
var deferred = $q.defer();
deferred.resolve({name : 'TestName', country : 'TestCountry'}));
return deferred.promise;
});

How to test Angular controller having a service

I am having a controller and service like below
(function () {
var mockController = function ($scope, MockService) {
$scope.message = "This is a text message";
$scope.getCities = function () {
$scope.places = [];
MockService.getCities().then(function (response) {
var places = response.data["weather-app:root"].city;
if (places) {
if (Array.isArray(places)) {
$scope.places = places;
} else {
$scope.places.push(places);
}
}
});
};
};
var mockService = function ($http) {
this.getCities = function () {
return $http.get("../rest/url", {
headers: {
'Accept': 'application/yang.data+json'
}
});
};
};
angular.module("MockApp", [])
.service("MockService", mockService)
.controller("MockController", mockController);
}())
I created a mock service like below for mocking the service for unit testing.
(function () {
angular.module('mock.service', [])
.service('MockService', function ($q) {
var mockService = {};
mockService.getCities = function () {
var mydata = {
"weather-app:root": {
"city": [
{
"city-name": "Chennai"
, "country-name": "India"
}
, {
"city-name": "Mangalore"
, "country-name": "India"
}
]
}
}
return $q.when(mydata);
};
return mockService;
});
}());
My test case is like
describe("MockController", function () {
var $scope;
beforeEach(function () {
module("MockApp");
beforeEach(module('mock.service'));
inject(function (_$controller_, _$rootScope_, _MockService_) {
$scope = _$rootScope_.$new();
controller = _$controller_("MockController", {
$scope: $scope
, MockService: _MockService_
});
});
});
describe("Test", function () {
it("Should be Bangalore", function () {
$scope.getCities();
console.log($scope.places);
});
});
});
the problem is that the then method in controller is not getting called. How can I resolve the issue ?
Three things to fix...
Don't nest the beforeEach calls. You can init multiple modules with module.
beforeEach(function() {
module('MockApp', 'mock.service');
// and so on
Your mock data does not quite match what you'd see from an $http based promise response
return $q.when({data: mydata});
In order to process promises, you need to trigger a digest cycle
it("Should be Bangalore", function() {
$scope.getCities();
$scope.$apply();
console.log($scope.places);
});

Angular factory testing with Karma Jasmine. Says UnKnown provider

This is my angular code
var app = angular.module('FleetTask', ['ui.router']);
app.constant('settings', {
ApiURL: 'http://localhost/FleetTask.API',
MsgDuration: 4000,
ProductURL: '/api/product',
ProductGroupURL: '/api/productgroup',
DataAreaURL: '/api/dataarea'
});
app.factory('ProductGroupFactory', ['$http', 'settings', function ($http, settings) {
return {
getProductGroupList: function () {
return $http.get(settings.ApiURL + settings.ProductGroupURL);
},
getProductGroupByID: function (id) {
return $http.get(settings.ApiURL + settings.ProductGroupURL + "/" + id);
},
saveProductGroup: function (productGroup) {
return $http.put(settings.ApiURL + settings.ProductGroupURL, productGroup);
},
deleteProductGroup: function (id) {
return $http.delete(settings.ApiURL + settings.ProductGroupURL + "/" + id);
},
getProductList: function () {
return $http.get(settings.ApiURL + settings.ProductURL);
},
getDataAreaList: function (lookup) {
return $http.get(settings.ApiURL + settings.DataAreaURL + "/dataarealist/" + lookup);
}
};
}]);
app.controller('ProductGroupController', function ($scope, ProductGroupFactory, $rootScope, settings) {
function init(pageIndex) {
$rootScope.pageTitle = "Product Group";
// Complete functionality
};
init();
});
This is my test spec:
describe('FleetTask', function () {
beforeEach(function () {
module('FleetTask');
module('ui.router');
});
describe('ProductGroupFactory', function () {
var scope, controller, productGroupFactory, settings;
beforeEach(inject(function ($injector, $rootScope, $controller, $provide) {
scope = $rootScope.$new();
settings = $injector.get('settings');
productGroupFactory = {
save: jasmine.createSpy()
};
$provide.value('ProductGroupFactory', productGroupFactory);
}));
it('Access Factory', function () {
expect(settings).toBeDefined();
});
});
});
This is the error I am getting
Error says UnKnown provider. Looks like I am not doing right way of injecting factory. 'settings' service got injected correctly, but not the factory. Help me to find out right process to inject factory. Thanks
Answer: Derived test spec I have made out of answers.
describe('FleetTask', function () {
var scope, controller, pgFactory, constants, httpBackend;
beforeEach(function () {
module('FleetTask');
module('ui.router');
module('angulartics');
module('angulartics.google.analytics');
inject(function (_settings_, $injector, $controller, _$rootScope_) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
constants = _settings_;
pgFactory = function () {
return $injector.get('ProductGroupFactory');
};
httpBackend = $injector.get('$httpBackend');
httpBackend.whenGET(constants.ApiURL + constants.ProductURL).respond(function (data) { scope.products = data; });
httpBackend.expectGET(constants.ApiURL + constants.ProductGroupURL);
httpBackend.expectGET(constants.ApiURL + constants.DataAreaURL + "/dataarealist/0");
controller = $controller;
controller('ProductGroupController', { '$scope': scope });
});
});
it('Check if settings service is defined', function () {
expect(constants).toBeDefined();
});
it('Check if ProductGroupFactory is defined', function () {
expect(pgFactory).toBeDefined();
});
it('Check if controller is defined', function () {
expect(controller).toBeDefined();
});
it('Access Controller', function () {
expect(scope.error).toBe('');
});
it('Check if controller is defined', function () {
var prds = scope.products;
var len = prds.length;
expect(len).toBe(0);
});
});
You need to access $provide as a part of module specification, it can't be injected. i.e
beforeEach(module("FleetTask", function ($provide) {
productGroupFactory = jasmine.createSpyObj('productGroupFactory', ['save']);
$provide.value('ProductGroupFactory', productGroupFactory);
}));
beforeEach(inject(function (_settings_, $rootScope, $controller) {
scope = $rootScope.$new();
settings = _settings_;
//...
}));
it('Access Factory', function () {
expect(settings).toBeDefined();
});

Unit testing promises in AngularJS with arguments

We're unit testing our services and facing issue spying on methods with arguments of dependent services.
I am writing unit tests for ServiceA
ServiceA.js
angular.module("App").service("ServiceA", function($http, ServiceB) {
this.detail = null;
this.method = function(id){
var sevrB = new ServiceB();
return sevrB.getId(1).then(function(response) {
this.detail = response.data;
});
};
});
ServiceB.js (is a factory)
(function () {
var dependencies = [
'../module'
];
define(dependencies, function (module) {
return module.factory('ServiceB', function ($http) {
var ServiceB= function () {
this.id = null;
};
ServiceB.prototype.getId = function(Id) {
return $http.get('/test/');
}
}
}());
Unit test code
describe('Testing ServiceA', function () {
var serviceA, serviceBMock;
beforeEach(function () {
var _serviceBMock = function () {
return {
getId:function(id){
return 'test';
}
};
};
angular.module('ServiceAMocks', [])
.value('ServiceB', _serviceBMock);
});
beforeEach(module('ServiceAMocks'));
beforeEach(inject(function (_ServiceA_, _ServiceB_) {
serviceA=_ServiceA_;
serviceBMock=_ServiceB_;
});
it('retrive Id', function () {
spyOn(serviceBMock,'getId').and.Return('test');
serviceA.method(1);
});
});
I am spying on getId method of ServiceB from ServiceA and if i mocked ServiceB as function i am getting error below
Error: getId() method does not exist
at jasmineInterface.spyOn
If I mock serviceB as object then i get error as
TypeError: object is not a function
var _serviceBMock = {
getId:function(id){
return 'test';
}
}
And I am not sure of testing promise in this scenario.
This version supports Jasmine 1.3
I’m injecting $q service as ServiceB wants to call method then. We can even go forward and resolve returned promise, but this is next step in testing.
Answer to previous version of question, where AngularJS injects instance of serviceB
describe('ServiceA', function () {
var serviceA, ServiceB, $q;
beforeEach(function () {
module('App');
});
beforeEach(function () {
module(function ($provide) {
$provide.value('ServiceB', {
getId: jasmine.createSpy('ServiceB.getId').andCallFake(function () {
return $q.all();
})
});
});
});
beforeEach(inject(function (_ServiceA_, _ServiceB_, _$q_) {
$q = _$q_;
serviceA = _ServiceA_;
ServiceB = _ServiceB_;
}));
describe('.method()', function () {
it('returns ServiceB.getId() argument', function () {
serviceA.method(1);
expect(ServiceB.getId).toHaveBeenCalledWith(1);
});
});
});
jsfiddle: http://jsfiddle.net/krzysztof_safjanowski/sDh35/

Resources