I'm new at unit testing of Angular with Jasmine, I have been struggling with this code for a few hours, I went through a lot articles and answers here, ,but, I can't find the solution.
The services are mocks as you can see. The problem is that, the code below throws that error it does not find the authenticationService variable. However, based on the info I collected today, it should be injected into the "it" method.
If I rewrite the code and the necessary stuff is injected at the "it" method than it is works. But, it is ugly and not the way which should be followed, because it causes boilerplate code.
What I'm doing wrong?
I tried to move the inject(function ($rootScope, $controller) block to over creating the mocks but it didn't help, and later I understood that there is a proper order in injecting should be kept in mind. So I put them back in the nested describe block.
The purpose of the test is check whether the authenticationService.authenticate() is called when the event:auth-loginRequired is fired. I'm not sure whether the assert part of the code is correct. I'm going to work on it once the issue I described above is fixed.
describe('dilibShell module speccifications', function ()
{
var $location, $rootScope, scope, AuthenticationService, AuthService, Common, ShellController;
beforeEach(module('dilibShell'));
beforeEach(function ()
{
AuthenticationService = {
authenticate: function (user)
{
return 'user';
}
}
AuthService = {
loginConfirmed: function () { }
}
Common = {
activateController: function () { },
logger: {
getLogFn: function () { }
}
}
module(function ($provide)
{
$provide.value('authenticationService', AuthenticationService);
});
module(function ($provide)
{
$provide.value('authService', AuthService);
});
module(function ($provide)
{
$provide.value('common', Common);
});
});
describe('shellController Specification', function ()
{ beforeEach(function ()
{
inject(function ($rootScope, $controller)
{
rootScope = $rootScope;
scope = $rootScope.$new();
ShellController = $controller('shellController', {
$scope: scope
});
});
});
it('When event:auth-loginRequired is caught then authenticationService.authenticate must be invoked.',
(function ()
{
expect(authenticationService.authenticate()).toBe('user');
//arrange
//spyOn(authenticationService, 'authenticate');
scope.$digest();
//act
rootScope.$broadcast('event:auth-loginRequired');
//assert
expect(authenticationService.authenticate).toHaveBeenCalledWith();
}));
});
});
Update:
Based on the answers here I modified my code and the it block looks like below and it is working fine, not counting another error in the code, but that one is over this scope.
So, my question is that it is inevitable that, in case of injecting services mocks, I have to call inject in the particular it blocks?
it('When event:auth-loginRequired is caught then authenticationService.authenticate must be invoked.',
(inject(function (authenticationService)
{
expect(authenticationService.authenticate()).toBe('user');
//arrange
spyOn(authenticationService, 'authenticate');
//scope.$digest();
//act
rootScope.$broadcast('event:auth-loginRequired');
//assert
expect(authenticationService.authenticate).toHaveBeenCalledWith();
})));
Related
I have an angular factory like this:
.factory('widgetFactory', ['$http', function($http){
function getWidgets(){
return $http.get('http://example.com/api/widgets/')
.then(function(response){
return response;
});
}
return {
getWidgets:getWidgets
};
}])
And I have the following jasmine test:
describe('widgetFactory', function ($q) {
var mockHttp,
fakeResponse = 'response'
beforeEach(function() {
mockHttp = {
get: jasmine.createSpy('get spy').and.callFake(function () {
var deferred = $q.defer();
deferred.resolve(fakeResponse);
return deferred.promise;
})
};
module(function ($provide) {
$provide.value('$http', mockHttp);
});
});
it('should call api when getWidgets is called', inject(function (widgetFactory) {
var result;
widgetFactory.getWidgets().then(function(response){
result = response;
});
expect(mockHttp.post).toHaveBeenCalledWith('http://example.com/api/widgets/');
expect(result).toBe(fakeResponse);
}));
});
But I get the following error: describe does not expect a done parameter
I think it may be to do with how I'm using $q in my test (other examples I've seen have inject(function($q){ ... inside the beforeEach, but I can't due to my use of module inside beforeEach as this then gives me the following error: Injector already created, can not register a module!)
Any ideas?
You can't inject in describe method. Here I've reworked your version to use ngMock and get rid of mockHttp. I hope it explains a little bit how ngMock works
describe('widgetFactory', function () {
var mockHttp,
fakeResponse = 'response',
getWidgetsDefer,
getWidgetsPromise,
$httpBackend,
widgetFactory,
$q;
beforeEach(module('plunker'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$q = $injector.get('$q');
widgetFactory = $injector.get('widgetFactory');
}));
beforeEach(function() {
getWidgetsDefer = $q.defer();
$httpBackend.when('GET', 'http://example.com/api/widgets/')
.respond(getWidgetsDefer);
getWidgetsPromise = widgetFactory.getWidgets();
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should call api when getWidgets is called', inject(function (widgetFactory) {
expect($httpBackend.flush).not.toThrow();
}));
});
You can find plunker here
'done' function is the argument that is expected in Jasmine blocks, but not in describe, that's the meaning of the error. Angular services can't be injected without inject wrapper because Jasmine is unaware of them, and the problem can't be solved just by ignoring this fact.
angular.mock.module is able to mock services with object argument, there's no need to re-invent the wheel.
Unfortunately, mocked services are meant to be self-contained, and it won't solve the problem with $q, so it has to be injected in addition after module:
var $q;
beforeEach(function() {
module({
$http: { ... }
});
inject(function (_$q_) {
$q = _$q_;
});
})
Fortunately, ngMock provides $httpBackend mock, so mocking $http is pointless. In fact, real request shouldn't (and can't) be performed with ngMock. The spec for widget service becomes as slim as that:
widgetFactory.getWidgets();
$httpBackend.expect('GET', 'http://example.com/api/widgets/').respond(fakeResponse);
expect($httpBackend.flush).not.toThrow();
Notice that it doesn't matter if the request was mocked before or after $http.get call, the requests are solved when $httpBackend.flush() is called. And fakeResponse === fakeResponse check can be safely skipped as well.
I'm trying to test a controller that uses a service. When I run the following test and breakpoint within the inject function, the actual service is injected instead of the mock service. Why is the service defined through the $provider.factory not being injected?
"use strict";
describe("contestantController", function () {
var dataService, rootScope, scope, passPromise, contestantController;
beforeEach(function(){
module(function ($provide) {
//mock service
$provide.factory('contestantDataService', ['$q', function ($q) {
function save(data){
if(passPromise){
return $q.when();
} else {
return $q.reject();
}
}
function getData() {
if (passPromise) {
return $q.when(smallDataSet());
} else {
return $q.reject();
}
}
return {
addContestant: save,
getContestants: getData,
};
}]);
});
module('contestantApp');
});
beforeEach(inject(function ($rootScope, $controller, contestantDataService) {
rootScope = $rootScope;
scope = $rootScope.$new();
dataService = contestantDataService;
spyOn(dataService, 'getContestants').and.callThrough();
contestantController = $controller('contestantController', {
$scope: scope,
contestantDataService: dataService
});
}));
it('should call getContestants method on contestantDataService on calling saveData', function () {
passPromise = true;
rootScope.$digest();
expect(dataService.getContestants).toHaveBeenCalled();
expect(scope.contestants).toEqual(smallDataSet());
});
});
Tyler's answer will work, but the reasoning is a little off.
The module() function simply registers modules or module initialization functions that the inject() function will later use to initialize the Angular injector. It in no way links the mock service with the module.
Your code doesn't work because order in which the services are registered matters. Your code first registers a mock contestantDataService, then registers the contestantApp module, which contains its own contestantDataService, overwriting the registered mock. If you just move the module('contestantApp') call to the top, it should work as expected.
So this means that the two blocks below are equivalent and will both work...
beforeEach(function(){
module('contestantApp');
module(function ($provide) {
...
});
);
and
beforeEach(function(){
module('contestantApp', function ($provide) {
...
});
);
Thx for #Yunchi . You pointed out my mistake.
You should invoke the module function before you mock the service contestantDataService.
Just update your code into this way,
//make sure contesttantDataService exist in this module in your production environment.
var dataService, rootScope, scope, passPromise, contestantController;
beforeEach(function(){
//Add module name here which means we define the mockService under this module
module('contestantApp', function ($provide) {
//mock service
$provide.factory('contestantDataService', ['$q', function ($q) {
function save(data){
if(passPromise){
return $q.when();
} else {
return $q.reject();
}
}
function getData() {
if (passPromise) {
return $q.when(smallDataSet());
} else {
return $q.reject();
}
}
return {
addContestant: save,
getContestants: getData,
};
}]);
});
beforeEach(inject(function ($rootScope, $controller, contestantDataService) {
rootScope = $rootScope;
scope = $rootScope.$new();
dataService = contestantDataService;
spyOn(dataService, 'getContestants').and.callThrough();
//no need to manually inject to our controller now.
contestantController = $controller('contestantController', {
$scope: scope
});
}));
Instead of using $provide.factory I think it's better to use $provide.value in our unit test. Because we only need to make sure the contestantDataService is an object and have the function.
Here are some similar questions you can check,
Injecting a mock into an AngularJS service.
Mock a service in order to test a controller.
Here is the jsfiddle I just created.
Have fun. : )
We are building an AngularJS app following some of the best practice guidelines which are outlined here.
Am specifically interested in testing a very simple controller to get up and running with karma.
The controller code is:
angular.module('ttn').controller('Login', Login);
function Login(){
var login = this;
login.title = 'foo bar content here etc';
}
And the spec code is:
describe('Controller: Login', function () {
beforeEach(module('ttn'));
var scope, controller;
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
controller = $controller('Login', {
$scope: scope
});
scope.$digest();
}));
it('should define a title', function () {
expect(scope.title).toBeDefined();
});
});
This fails with expecting undefined to be defined.
If I change the controller to:
angular.module('ttn').controller('Login', Login);
function Login($scope){
$scope.title = 'foo bar whatsit jibber';
}
The test then passes as expected. I am not sure how to reference the controller written in the manner outlined on the above link to get the test to pass.
Since your controller doesn't use $scope, you shouldn't be injecting it and using it in your tests. Instead you should be checking for title on your controller:
describe('Controller: Login', function () {
beforeEach(module('ttn'));
var controller;
beforeEach(inject(function ($controller) {
controller = $controller('Login', {});
}));
it('should define a title', function () {
expect(controller.title).toBeDefined();
});
});
Plunkr
The following service uses $q.when to wrap a third-party promise:
// service.js
angular.module('test', [])
.service('pouchdb', function($q, $window) {
var db = new $window.PouchDB('test');
this.info = function() {
return $q.when(db.info.apply(db, arguments));
};
});
Corresponding unit test:
describe('Failing Q when tests', function() {
beforeEach(module('test'));
var $rootScope, pouchdb;
beforeEach(inject(function(_$rootScope_, pouchdb) {
$rootScope = _$rootScope_;
pouchdb = pouchdb;
}));
it('should resolve a promise', function(done) {
// FIXME: never resolves
pouchdb.info()
.then(function(info) {
expect(info).toBeDefined();
})
.finally(done);
$rootScope.$apply();
});
});
pouchdb.info never resolves and Jasmine times out. However, if I manually inject ng, the spec works as expected:
describe('Working Q when tests', function() {
var pouchdb;
beforeEach(function() {
var $injector = angular.injector(['ng', 'test']);
var pouchDB = $injector.get('pouchdb');
pouchdb = pouchDB('db');
});
it('should resolve a promise', function(done) {
pouchdb.info()
.then(function(info) {
expect(info).toBeDefined();
})
.finally(done);
});
});
Could anyone explain why;
The first spec doesn't resolve
The second spec does (injecting ng)
It doesn't need $rootScope.$apply
Whether it's a good pattern to use
Are you using angular-mocks? https://docs.angularjs.org/api/ngMock
The only reason why I think that you'd need to inject 'ng' manually is if there is no ng-app initializing your app, at least according to https://docs.angularjs.org/api/ng/function/angular.module
If you use angular-mocks it takes care of that for you https://github.com/angular/angular.js/blob/master/src/ngMock/angular-mocks.js#L1785
Can't think of any other reason as to why this problem would occur.
I'm trying to mock out this 'usermanager' provider in my controller test and I always end up getting this error
TypeError: Attempted to assign to readonly property.
at workFn (c:{...}/bower_components/angular-mocks/angular-mocks.js:2105)
Here is my controller:
angular.module('sasApp')
.controller('RegisterCtrl', function ($scope, usermanager) {
// variables
$scope.captcha = {};
// Get captcha from server
usermanager.getCaptcha().then(function (captcha) {
$scope.captcha = captcha;
}, function (reason) {
console.log('Failed: ' + reason);
});
});
And here is my test:
describe('Controller: RegisterCtrl', function () {
// load the controller's module
beforeEach(module('sasApp'));
var RegisterCtrl,
scope,
mockUsermanager;
mockUsermanager = {
getCaptcha: function () {
return {
test: 1
};
}
};
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
RegisterCtrl = $controller('RegisterCtrl', {
$scope: scope,
usermanager: mockUsermanager
});
}));
describe('Initialization', function () {
it('should have \'captcha\' object defined', function () {
expect(scope.captcha).toBeDefined();
});
});
});
The thing is, I didn't get this error until I tried setting up the mocking of the 'usermanager' provider. If I remove the line 'usermanager: mockUsermanager' in the beforeEach statement, then it all runs well.
Can you spot what is wrong ? Thanks in advance!
It might not be the best answer but I've made a Plnkr that does not reproduce the issue here:
http://plnkr.co/GgUeKnsvxEHmD3Wcyocx?p=preview
A quick note:
usermanager.getCaptcha() has to return a promise otherwise the next part of test would blow up when it tries calling .then. So I've just put in a bogus one in.
Let me know if you spot any differences (I don't know what angular version you're on for example). And I can see about updating