How to Spy on a method of Service from controller? - angularjs

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

Related

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

How to restrict to invoke service dependencies while writing Unit test for Controller?

I have the following controller:
(function () {
"use strict";
angular.module('usp.configuration').controller('ConfigurationController', ConfigurationController);
ConfigurationController.$inject = ['$scope', '$rootScope', '$routeParams', 'configurationService'];
function ConfigurationController($scope, $rootScope, $routeParams, configurationService) {
//Get Master Gas List
configurationService.getMasterGasList().then(function (response) {
$scope.masterGasList = response.data.data;
});
$scope.convertToInt = function (str) {
if (!isNumberEmpty(str) && !isNaN(str)) {
return parseInt(str, 10);
}
return "";
}
$scope.convertToString = function (num) {
if (!isNumberEmpty(num) && !isNaN(num)) {
return num + "";
}
return "";
}
}
}());
And below is the test case for the controller:
describe("test suite for Configuration test controller", function() {
var scope = null;
var configurationService;
beforeEach(module("usp.configuration"));
beforeEach(inject(function($rootScope, $controller, _configurationService_) {
// Services
// _'s are automatically unwrapped
configurationService = _configurationService_;
// Controller Setup
scope = $rootScope.$new();
$controller("ConfigurationController", {
$scope: scope,
configurationService : configurationService
});
}));
it("should convert to int", function() {
expect(scope.convertToInt("2")).toEqual(2);
});
it("should return empty string", function() {
expect(scope.convertToInt("asd")).toEqual("");
});
});
I don't want to call that service while I am running the test case.
I am new to unit testing, I don't know how can I do this.
Please help me to do this?
You need to mock the dependencies with $provide
beforeEach(function () {
configurationServiceMock = {
getSomething: function () {
return 'mockReturnValue';
}
};
module(function ($provide) {
$provide.value('configurationService', configurationServiceMock);
});
});
see: Injecting a mock into an AngularJS service
Solution for your needs:
var configurationServiceMock = {
getMasterGasList: function () {
return {
then: function(callback) {}
};
}
};
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('ConfigurationController', {
'$scope': scope,
'configurationService': configurationServiceMock
});
}));

Unit testing angularjs with mocha/chai should.have.been.called not working

The following test passes whether my expect statement is
expect(propertyFactory.readProperty).should.have.been.called;
or
expect(propertyFactory.readProperty).should.have.not.been.called;
I've tried this with or without the sinon stub.
describe('pf.propertyModule', function () {
var controller;
var propertyFactory;
beforeEach(module('pf.core'));
beforeEach(module('pf.propertyModule'));
beforeEach(inject(function(_propertyFactory_) {
propertyFactory = _propertyFactory_;
/*
sinon.stub(propertyFactory, 'readProperty', function() {
return {
data: {
property: 'xyz'
}
}
});*/
}));
describe('readPropertyCtrl', function () {
it('should have a passing first test', function () {
expect(propertyFactory.readProperty).should.have.been.called;
});
});
});
UPDATE: Ok, so I got past the initial "non-error" by ensuring "sinon-chai" was installed, but now I'm getting the output: thrown: [Function] } is not a spy or a call to a spy!
UPDATE: Ok, so I got the previous error working by calling sinon.spy(). However, now I'm wondering how one would go about mocking a method that is expected to return a promise. I'm getting the error " TypeError: 'undefined' is not an object (evaluating 'propertyFactory.readProperty({id: $stateParams.id}).then')
"
Updated code:
describe('pf.propertyModule', function () {
var ctrl;
var scope;
var propertyFactory;
beforeEach(module('pf.core'));
beforeEach(module('pf.propertyModule'));
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
propertyFactory = {
readProperty: sinon.spy()
};
ctrl = $controller('readPropertyCtrl as vm', {
$scope: scope,
propertyFactory: propertyFactory
});
}));
describe('readPropertyCtrl', function () {
it('should have a passing first test', function () {
expect(scope.vm).to.be.defined;
expect(propertyFactory.readProperty).should.have.been.called;
});
});
});
Code to test:
propertyFactory.readProperty({id: $stateParams.id}).then(getPropertySuccess);
function getPropertySuccess(response) {
vm.loadedProperty = response.data.property;
}
Updated Code:
describe('pf.propertyModule', function () {
var ctrl;
var scope;
var propertyFactory;
beforeEach(module('pf.core'));
beforeEach(module('pf.propertyModule'));
beforeEach(inject(function($rootScope, $controller, $q, _propertyFactory_) {
scope = $rootScope.$new();
propertyFactory = _propertyFactory_;
var deferred = $q.defer();
var promise = deferred.promise;
sinon.stub(propertyFactory, "readProperty").returns(promise);
deferred.resolve({property: 'xyz'});
ctrl = $controller('readPropertyCtrl as vm', {
$scope: scope,
propertyFactory: propertyFactory
});
}));
describe('readPropertyCtrl', function () {
it('should have a passing first test', function () {
expect(scope.vm).to.be.defined;
expect(propertyFactory.readProperty).should.have.been.called;
});
});
});
This attempt is returning thrown: [Function] } is not a spy or a call to a spy!

Angular Unit Test Jasmine Spy error

The following controller is getting a TypeError: 'undefined' is not a function (evaluating sessionService.getCurrentPlace()). I have a Mock Service with that method being spied on. The other method on the mock service works fine. I've tried .AndReturns({..}) on the spy as well as .AndCallThrough() but no luck. Any idea what I'm missing, or am I going about this wrong? Much Thanks!
CONTROLLER:
'use strict';
angular.module('wallyApp')
.controller('addGatewayCtrl', function ($scope, $location, $filter, sessionService) {
/*
private members
*/
//set scope from session data
$scope.processSession = function (places) {
$scope.currentPlaceId = sessionService.getCurrentPlace();
if (!$scope.currentPlaceId) {
$scope.currentPlaceId = places[0].id;
}
$scope.place = $filter("getById")(places, $scope.currentPlaceId);
$scope.ready = true;
};
/*
setup our scope
*/
$scope.currentPlaceId = null;
$scope.place = {};
$scope.videoSrc = "/videos/gateway-poster.gif";
$scope.loaded = true;
/*
setup controller behaivors
*/
//set video or gif to show or hide video
$scope.setVideo = function () {
$scope.videoSrc = "/videos/gateway.gif";
};
$scope.setPoster = function () {
$scope.videoSrc = "/videos/gateway-poster.gif";
};
//initialize scope
$scope.setVideo();
//submit form
$scope.continue = function () {
$location.path("/setup/pair-gateway");
return false;
};
//cancel
$scope.back = function () {
$location.path("/setup/plan-locations");
return false;
};
//wifi
$scope.gotoWifi = function () {
$location.path("/setup/wifi");
return false;
};
/*
setup our services, etc
*/
//get our places from the cache
sessionService.get("places").then(function (places) {
if (!places || places.length < 1) {
sessionService.refreshPlaces(); //Note we don't care about the promise as our broadcast watch will pick up when ready
} else {
$scope.processSession(places);
}
}).catch(function (error) {
//TODO:SSW Call Alert Service??
});
//Watch broadcast for changes
$scope.$on("draco.placesRefreshed", function (event, data) {
sessionService.get("places").then(function (places) {
$scope.processSession(places);
});
});
});
UNIT TEST:
'use strict';
describe('addGatewayCtrl', function () {
var $q,
$rootScope,
$location,
$scope,
$filter,
mockSessionService,
completePath = "/setup/pair-gateway",
backPath = "/setup/plan-locations",
wifiPath = "/setup/wifi",
sessionDeferred,
sessionInitDeferred,
mockPlaces = [{ id: "0001" }];
beforeEach(module('wallyApp'));
beforeEach(inject(function (_$q_, _$rootScope_, _$location_, _$filter_) {
$q = _$q_;
$location = _$location_;
$rootScope = _$rootScope_;
$filter = _$filter_;
}));
beforeEach(inject(function ($controller) {
$scope = $rootScope.$new();
mockSessionService = {
get: function (contact) {
sessionDeferred = $q.defer();
return sessionDeferred.promise;
},
getCurrentPlace: function () {
return mockPlaces[0].id;
},
refreshPlaces: function () {
sessionInitDeferred = $q.defer();
return sessionInitDeferred.promise;
}
};
spyOn(mockSessionService, 'get').andCallThrough();
spyOn(mockSessionService, 'getCurrentPlace').andReturn(mockPlaces[0].id);
spyOn(mockSessionService, 'refreshPlaces').andCallThrough();
$controller('addGatewayCtrl', {
'$scope': $scope,
'$location': $location,
'$filter':$filter,
'sessionService': mockSessionService
});
}));
describe('call session service to get place data ', function () {
//resolve our mock place and session services
beforeEach(function () {
//resolve mocks
sessionDeferred.resolve(mockPlaces);
$rootScope.$apply();
});
//run tests
it('should have called sessionService get places', function () {
expect(mockSessionService.get).toHaveBeenCalledWith("places");
});
it('should have called sessionService get currentPlaceId', function () {
expect(mockSessionService.getCurrentPlace).toHaveBeenCalled();
});
it('should have set scope', function () {
expect($scope.place).toEqual(mockPlaces[0]);
});
});
});
So I figured it out. With nested deferred's you have to call $scope.$apply() in between. The following fixed it up (along with a few minor changes to the mock data responses, but those were trivial):
//resolve promises
activityMessagesDeferred.resolve(mockActivityMessages);
$rootScope.$apply();
$rootScope.$broadcast("draco.sessionRefreshed");
activityCountDeferred.resolve(mockActivityCount);
$rootScope.$apply();
placesDeferred.resolve(mockPlaces);
activityListDeferred.resolve(mockActivities);
$rootScope.$apply();

Resources