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/
Related
I have a simple enough function that closes an $mdSidenav instance in my application
function closeSideNav() {
$mdSidenav('left').close();
}
I'm now needing to unit test this, but am having trouble writing an expectation for the close() call on $mdSidenav.
I thought about using $provide in my test spec
module(function($provide) {
$provide.value('$mdSidenav', function(id) {
return {
close: jasmine.createSpy('$mdSidenav.close')
}
})
});
beforeEach(inject(function(_$controller_, _$mdSidenav_) {
$controller = _$controller_;
$mdSidenav = _$mdSidenav_;
}));
beforeEach(function() {
vm = $controller('NavbarController', {
$mdSidenav: $mdSidenav
});
});
describe('vm.closeSideNav', function() {
beforeEach(function() {
spyOn($mdSidenav, 'close');
vm.closeSideNav()
});
it('should call $mdSidenav.close()', function() {
expect($mdSidenav.close).toHaveBeenCalled();
});
});
This throws a couple of errors:
Error: close() method does not exist
Error: Expected a spy, but got undefined.
Has anyone managed to mock out $mdSidenav and offer me some guidance please?
Thanks
UPDATE
Based on the suggested answer, I have now updated my test spec to
'use strict';
describe('NavbarController', function() {
var $controller,
vm,
$mdSidenav,
sideNavCloseMock;
beforeEach(function() {
module('app.layout');
sideNavCloseMock = jasmine.createSpy();
module(function($provide) {
$provide.value('$mdSidenav', function() {
return function(sideNavId) {
return {close: sideNavCloseMock}
}
})
});
});
beforeEach(inject(function(_$controller_, _$mdSidenav_) {
$controller = _$controller_;
$mdSidenav = _$mdSidenav_;
}));
beforeEach(function() {
vm = $controller('NavbarController', {
$mdSidenav: $mdSidenav
});
});
describe('vm.closeSideNav', function() {
beforeEach(function() {
vm.closeSideNav()
});
it('should call $mdSidenav.close()', function() {
expect(sideNavCloseMock).toHaveBeenCalled();
});
});
});
And for a sanity check, my actual controller looks as follows:
(function () {
'use strict';
angular
.module('app.layout')
.controller('NavbarController', Controller);
Controller.$inject = ['$mdSidenav'];
function Controller($mdSidenav) {
var vm = this;
vm.closeSideNav = closeSideNav;
//This only affects the sideNav when its not locked into position, so only on small\medium screens
function closeSideNav() {
$mdSidenav('left').close();
}
}
})();
Unfortunately this still isn't working for me, and I end up with a different error
TypeError: undefined is not a constructor (evaluating '$mdSidenav('left').close())
close method doesn't belong to $mdSidenav. $mdSidenav is a function that returns a side nav object. That's why it complains 'close() method does not exist'.
What you can do is mock the $mdSidenav to return an object hat has mocked close method, like this: -
var sideNavCloseMock;
beforeEach(module(function($provide){
sideNavCloseMock = jasmine.createSpy();
$provide.factory('$mdSidenav', function() {
return function(sideNavId){
return {close: sideNavCloseMock};
};
});
}));
then do
it('should call $mdSidenav.close()', function() {
expect(sideNavCloseMock).toHaveBeenCalled();
});
I am new to angularjs unit testing. I have a factory I am trying to spy on with jasmine and I can't figure out the syntax for the test spec. Below is the factory:
app.factory('assetFactory', function ($http) {
var baseAddress = "../api/";
var url = "";
var factory = {};
factory.getAssets = function (term) {
url = baseAddress + "asset/search/" + term;
return $http.get(url);
};
return factory;
});
Here is my test spec, which fails on the expect statement (Error: Expected spy getAssets to have been called):
describe('assetFactory', function () {
beforeEach(function () {
module('fixedAssetApp');
});
beforeEach(inject(function (assetFactory) {
spyOn(assetFactory, 'getAssets').and.callThrough();
}));
it('should be defined', inject(function (assetFactory) {
expect(assetFactory).toBeDefined();
}));
it('should have been called, inject(function (assetFactory) {
expect(assetFactory.getAssets).toHaveBeenCalled();
}));
});
Please add this change.
beforeEach(inject(function (assetFactory) {
spyOn(assetFactory, 'getAssets').and.callThrough();
assetFactory.getAssets();
}));
In order to toHaveBeenCalled() return true, you must called your function either in beforeEach or it block.
In my Controller I've defined the following service:
CrudService.getAllGroups().$promise.then(
function (response) { $scope.groups = response; },
function (error) { //error code.. }
);
Well, I want to test this service whether it gets a response or not. In test script at first I've defined a function to check whether the service is defined at all.
Test code:
describe('Ctrl: TestCtrl', function () {
beforeEach(module('testApp'));
var scope,
CrudService,
ctrl,
backend;
beforeEach(inject(function ($controller, $rootScope, _CrudService_, $httpBackend) {
scope = $rootScope.$new();
ctrl = $controller('TestCtrl', {
$scope: scope
});
CrudService = _CrudService_;
backend = $httpBackend;
}));
it('should defined the service getGroups', function () {
expect(CrudService.getGroups).toBeDefined();
});
//this is wrong!
it('should returns a successful response', function () {
backend.expectGET('http://localhost:63831/api/group').respond(200, 'success');
backend.flush();
});
});
I don't know how to get a response in the test. I'm new in unit testing and need some help.
For a better comprehension here is the service code:
//CrudService file:
...
return {
getAllGroups: function () {
return ResService.group.query();
}
}
...
//ResService file:
return {
group: $resource(baseUrl + '/api/group/:Id', {
Id: '#Id'
}, {})
}
Do anyone has an idea?
It's incorrect in the sense that it's not a unit test. If you are testing controller here, then you should mock CrudService and test that $scope.groups has been assigned correctly.
beforeEach(function () {
module(function ($provide) {
$provide.factory('CrudService', function () {
return {
getAllGroups: function () {
return {
$promise: null // return an actual promise here
}
}
}
});
});
});
it('should set groups', function () {
expect($scope.groups).toEqual('success')
});
And you need a separate spec to test if CrudService calling backend correctly.
I want to test if my service call was successfull and if the function "search" was called after the service call.
My Controller:
function UnitTestsCtrl(homePSrv, $scope) {
var that = this;
this.SearchModel = {};
this.homePSrv = homePSrv;
}
UnitTestsCtrl.prototype.Init = function() {
var that = this;
this.homePSrv.InitUnitTestSearchModel().then(function(result) {
that.SearchModel = result;
that.Search();
});
}
angular.module("unitTestsCtrl", [])
.controller("unitTestsCtrl", UnitTestsCtrl);
The jasmine unit test:
describe('Controller Tests: "UnitTestsCtrl"', function () {
var ctrl, mockHomePSrv;
beforeEach(function () {
mockHomePSrv = jasmine.createSpyObj('homePSrv', ['InitUnitTestSearchModel']);
module('app.main');
inject(function ($controller, $q) {
mockHomePSrv.InitUnitTestSearchModel.and.returnValue($q.when('InitUnitTestSearchModelData'));
ctrl = $controller('unitTestsCtrl', {
homePSrv: mockHomePSrv,
});
});
});
it('Funktion "Search" was called', function () {
spyOn(ctrl, 'Init');
spyOn(ctrl, 'Search');
ctrl.Init();
expect(ctrl.Init).toHaveBeenCalled();
//Thats not working - its not being called
expect(ctrl.Search).toHaveBeenCalled();
});
});
The problem is that the expectation if the search function was called is false. I think it has to do with the promise.
Solution:
spyOn(ctrl, 'Search');
ctrl.Init();
$scope.$digest() //I've inserted this and everything works fine now
expect(ctrl.Init).toHaveBeenCalled();
expect(ctrl.Search).toHaveBeenCalled();
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