I am using an AngularJS service in my app. I've learned that the service I've created is unreliable. For that reason, I want to build some unit tests around it. While the service works fine in my AngularJS app, I'm having problems getting it to work with Jasmine. Its like I can't load any modules. I've strip down the code to the bare bones. I do not understand what I'm doing wrong. My code looks like this:
myService.js
'use strict';
angular.module('customModule', [])
.factory('$serviceName', [function () {
return {
isAvailable: function () {
return true;
}
};
}]
);
myService.spec.js
describe('customModule', function() {
beforeEach(function() {
console.log('loading module...');
module('customModule');
});
describe('$serviceName', function () {
var myService = null;
beforeEach(inject(function ($serviceName) {
console.log('loading service...');
myService = $serviceName;
}));
it('should work', function () {
console.log('testing');
var isAvailable = myService.isAvailable();
console.log(isAvailable);
expect(1 + 2).toEqual(3);
});
});
})
gruntfile.js
'use strict';
module.exports = function (grunt) {
grunt.initConfig({
jasmine: {
unitTests: {
src: 'test/*.js',
}
}
});
require('load-grunt-tasks')(grunt);
grunt.registerTask('default', ['jasmine:unitTests']);
};
My jasmine tests are running. However, its like myService.js isn't being loaded. I'm not sure how to do that. I'm also not sure how to get 'angular' (used in myService.js) in the tests either.
Thank you for your help.
Do you load you Angular application in myService.spec.js? Otherwise $serviceName is not available for dependency injection.
You should add something like this in your test file:
beforeEach(module('angularApp'));
(of course replace angularApp with the name you are using)
And it is also a good idea to use another beforeEach block to handle the dependency injection so you can use it in all tests in myService.spec.js. In total you should have something like this:
describe('serviceName', function () {
beforeEach(module('angularApp'));
var iMyService
// Initialize the service
beforeEach(inject(function ($serviceName) {
iMyService = $serviceName;
}));
it('should check if available', function () {
var isAvailable = iMyService.isAvailable();
console.log(isAvailable);
expect(1 + 2).toEqual(3);
});
});
Related
I have service that looks like that:
angular.module('app').service('MyService' , function (dependency1, dependency2, dependency3 ...) {
function funcToTest() {
// Do something
}
}
How could I inject a specific dependency to the service? For example, I want to inject only dependency2 to my service and I don't care about the other dependencies.
Unlike unit-testing Angular controllers, we have no way of directly passing dependencies. This is where $provide service comes to rescue!
Here's a sample example:
beforeEach(module('myApp', function ($provide) {
mockDependecy2 = {
mockFunction: function() {}
};
$provide.value('dependency2', mockDependecy2);
}));
Then, you can write your specs normally:
beforeEach(inject(function(_MyService_, ...) {
...
MyService = _MyService_;
}));
describe("...", function() {
it("...", function() {
MyService.funcToTest();
// write expect statements here
})
})
As seen in the example, you can (optionally) enclose them with underscores which are ignored by the injector when the reference name is resolved.
This will automatically inject dependency2 in your service wherever it is used
var dependency2;
beforeEach(function () {
inject(function (dependency2){
dependency2 = dependency2;
});
}
Okay, so Im trying to implement unit tests for an AngularJS project using Jasmine 2.2.0 - and I am not able to get a basic example test working for a controller. Any help is appreciated.
Here is the code for the controller (the simplest one I have):
angular.module('app.alerts')
.controller('AlertCtrl', AlertCtrl);
AlertCtrl.$inject = ['$scope', 'AlertService'];
function AlertCtrl($scope, AlertService) {
var vm = this;
vm.closeAlert = function(index) {
AlertService.closeAlert(index);
}
vm.getAlertList = function() {
return AlertService.getAlertList();
}
}
And here is the spec file I am trying to run:
describe('myApp', function() {
describe('controller', function() {
var scope, controller;
var mockAlertService = { // simple mock service
closeAlert: function(e) {
console.log('close');
},
getAlertList: function() {
return [];
}
}
beforeEach(function() {
angular.mock.module('app.alert');
});
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('AlertCtrl as alertctrl', {
$scope: scope,
AlertService: mockAlertService
});
}));
it('should work: ', function() {
expect(true).toBe(true);
});
});
});
When I run the test I get the following error message:
Error: [$inject:modulerr] Failed to instantiate module app.alert due to:
[$injector:nomod] Module 'app.alert' is not available! You either misspelled
the module name or forgot to load it.
It seems that the injector doesn't know about the app.alert module, which means its not being properly mocked? Im including the angular-mocks.js file in my SpecRunner.html file. Can anybody see what Im doing wrong?
Haven't you misspelled app.alert and app.alerts:
angular.module('app.alerts')
.controller('AlertCtrl', AlertCtrl);
and
angular.mock.module('app.alert');
I have a simple service implemented like this
sameRoof
.factory('dbService', function (localStorageService, backendUpdate) {
return {
checkProfileAndFlat: function () {
return (localStorageService.get('profile') && localStorageService.get('flatshare'));
}
};
});
LocalStorage are ngModules installed with bower.
I am writint unit test
'use strict';
describe('Service: service taking care of asking the local database', function () {
var localStorageService;
var fakeDB = {'profile' : 'testProfile', 'flatshare' : 'flatshare'};
// load the service's module
beforeEach(module('frontApp'));
// instantiate service
var dbService;
beforeEach(inject(function (_dbService_, _localStorageService_) {
dbService = _dbService_;
localStorageService = _localStorageService_;
//mock localStorageService get/add
spyOn(localStorageService,'get').andCallFake(function(key){
return fakeDB[key];
});
}));
it('should check profile and flatshare', function () {
console.log(localStorageService.get('profile'));
expect( dbService.checkProfileAndFlat() ).toBe(false);
});
});
but i am having problems here,
TypeError: 'undefined' is not a function (evaluating 'spyOn ...)
seems like i am implementing in wrong way the spyOn
the answer is
//mock localStorageService get/add
spyOn(localStorageService,'get').and.callFake(function(key){
return fakeDB[key];
});
as i am using jasmine 2.3.4 and jasmine API has changed compared to the one 1.3
I have found many articles here how to test Angular's config phase and I was able to create my tests against restangular and LocalStorageModule module configuration. The only one I cannot solve yet is checking whether the interceptor was added or not. I do not need to test the service because it is a 3rd party stuff, I consider it is already tested - hopefully.
The question is that, how can I spy on $httpProvider.interceptors.push method which is called in configuration phase?
Thanks for any help in advance!
Here is my code:
(function () {
'use strict';
angular.module('myapp', [
// Angular modules
'ngAnimate',
'ngRoute',
// Custom modules
'myapp.layout',
// 3rd Party Modules
'LocalStorageModule',
'http-auth-interceptor',
'restangular'
])
.config(function (RestangularProvider) {
RestangularProvider.setBaseUrl('http://.../services/webapi/');
})
.config(function (localStorageServiceProvider) {
localStorageServiceProvider.setPrefix('myapp');
})
.config(function($httpProvider) {
$httpProvider.interceptors.push('authInterceptorFactory');
});
})();
'use strict';
describe('myapp configuration', function() {
var RestangularProvider,
localStorageServiceProvider,
$httpProvider;
//modules
beforeEach(function () {
angular.module('myapp.layout', []);
angular.module('http-auth-interceptor', []);
});
//providers
beforeEach(function () {
module('restangular', function(_RestangularProvider_) {
RestangularProvider = _RestangularProvider_;
spyOn(RestangularProvider, 'setBaseUrl').and.callThrough();
});
module('LocalStorageModule', function (_localStorageServiceProvider_) {
localStorageServiceProvider = _localStorageServiceProvider_;
spyOn(localStorageServiceProvider, 'setPrefix').and.callThrough();
});
module('myapp', function(_$httpProvider_) {
$httpProvider = _$httpProvider_;
spyOn($httpProvider.interceptors, 'push').and.callThrough();
});
//module('myapp');
inject();
});
describe('Restangular configuration', function() {
it('setBaseUrl is set up', function() {
expect(RestangularProvider.setBaseUrl).toHaveBeenCalled();
});
});
describe('localStorage configuration', function() {
it('setPrefix is set up', function () {
expect(localStorageServiceProvider.setPrefix).toHaveBeenCalled();
});
});
describe('$httpProvider configuration', function() {
it('an interceptor is added', function() {
expect($httpProvider.interceptors.push).toHaveBeenCalled();
});
});
});
I've just been doing this myself and its actually surprisingly easy. Below are two ways you can do this, the first is the way I would recommend.
The thing to keep in mind is when you initialise a module the config part will be run automatically, you can use this to either test directly or help set up a test.
Option 1 - Using a fake module to setup
describe('config sets $httpProvider interceptor', function () {
var $httpProvider;
beforeEach(function () {
// First we initialise a random module, we will get $httpProvider
// from it and then use that to spyOn.
module(function (_$httpProvider_) {
$httpProvider = _$httpProvider_;
spyOn($httpProvider.interceptors, 'push');
});
// Now we initialise the app we want to test, $httpProvider will be
// the spy from before.
module('myapp');
inject();
});
it('should add to $httpProvider interceptors', function () {
expect($httpProvider.interceptors.push)
.toHaveBeenCalledWith('authInterceptorFactory');
});
});
Option 2 - Using just your module
describe('config sets $httpProvider interceptor', function () {
var $httpProvider;
beforeEach(function () {
// First we initialise a your module, we will get $httpProvider
// from it and then use that to assert on.
module('myapp', function (_$httpProvider_) {
$httpProvider = _$httpProvider_;
});
inject();
});
it('should add to $httpProvider interceptors', function () {
expect($httpProvider.interceptors).toEqual(['authInterceptorFactory']);
});
});
Again, my recommendation (and the way I did it) is with option 1.
I have created an Env service which wraps up environment information, and I'm currently using $location.host() to determine what environment I'm in. How do I mock that in my tests?
I've read https://groups.google.com/forum/?fromgroups#!topic/angular/F0jFWC4G9hI, but it doesn't seem to work, for example:
describe("Env (environment) service", function() {
var Env;
beforeEach(module('App'));
beforeEach(inject(
['Env', function(e) {
Env = e;
}]
));
describe("for staging", function() {
beforeEach(inject(function($location, $rootScope) {
$location.host("http://staging-site.com");
$rootScope.$apply();
}));
it("returns envrionment as staging", function() {
expect(Env.environment).toEqual("staging");
});
it("returns .isStaging() as true", function() {
expect(Env.isStaging()).toBeTruthy();
});
});
});
I've also tried the $browser variant, but that doesn't work either. Any ideas?
The best way is to use spies IMHO: http://tobyho.com/2011/12/15/jasmine-spy-cheatsheet/
// inject $location
spyOn($location, "host").andReturn("super.domain.com");
var host = $location.host();
alert(host) // is "super.domain.com"
expect($location.host).toHaveBeenCalled();
Syntax for jasmine 2.0 and greater has changed as follows
// inject $location
spyOn($location, "host").and.returnValue("super.domain.com");
var host = $location.host();
alert(host) // is "super.domain.com"
expect($location.host).toHaveBeenCalled();
I had similar problem and have used $injector service. (I don't know if it is the simplest solution, but it worked for me :) )
Since $location cannot be relied in during tests I have prepared my own mock.
First, you need to create a factory method. (Or service or provider if you prefer - see https://gist.github.com/Mithrandir0x/3639232 for comparison):
function locationFactory(_host) {
return function () {
return {
/* If you need more then $location.host(), add more methods */
host: function () {
return _host;
}
};
};
}
Then before you create your 'Env', feed injector with this $location mock:
module(function ($provide) {
$provide.factory('$location', locationFactory('http://staging-site.com'));
});
Now every time your access $location in your code your mock is injected, so it returns whatever you need it to.
More on $provide method is in angular docs
Hope this helps you in the future.
Update: I see one place when you might have gone wrong (or which at least would be wrong in my solution). It seems like you are initiating you 'Env' module (which I guess calculates the data immediately) and only after this you change $location - which might be already too late.
There is a good example in the doc: https://docs.angularjs.org/guide/services
You want to make sure to use $provide before you instantiate the service. I like to use $injector in the testcase, to first decide the location then instantiate the service.
var mockLocation;
beforeEach(function() {
// control the $location.host() function
// which we fake in the testcases.
mockLocation = {
host: jasmine.createSpy()
};
module(function($provide) {
$provide.value('$location', mockLocation);
});
});
then
describe('staging server:', function() {
beforeEach(function() {
// mockLocation.host.andReturn('http://staging-site.com'); // jasmine 1.x
mockLocation.host.and.returnValue('http://staging-site.com');
});
it('returns envrionment as staging', inject(function($injector) {
Env = $injector.get('Env');
expect(Env.environment).toEqual('staging');
}));
});
and
describe('production server:', function() {
beforeEach(function() {
// mockLocation.host.andReturn('prod.example.com'); // jasmine 1.x
mockLocation.host.and.returnValue('prod.example.com');
});
it('returns envrionment as production', inject(function($injector) {
Env = $injector.get('Env');
expect(Env.environment).toEqual('production');
}));
});
Here's another way to mock $location, with Sinon.js
locationMock = {
path: sinon.spy()
};
And an example assertion:
locationMock.path.should.be.calledWith('/home');