Testing a AngularJS services with dependencies - unit-testing

I have som issues testing a simple service in AngularJS, which depends on another service.
The service looks a bit like this:
serviceModule.factory('filtersService', ['$rootScope', 'urlService', function($rootScope, urlService){
return {
getFilters: function(){
if(urlService.getPathName() == "test")
{
return "something";
}
}]);
In the start I just tried to use window.location.pathname instead of creating a service, but it seemed like the wrong way to be able to mock it. I therefore created the urlService, which is basically a simple wrapper around the window-object.
I then have my filtersServiceTest which looks like this:
describe('filtersService test', function(){
var filtersService, urlServiceMock;
beforeEach(module('App.services'));
beforeEach(function(){
urlServiceMock = {
getPathName: function(){
return "";
}
};
module(function($provide){
$provide.value('urlService', urlServiceMock);
});
});
it('Should return something if url is test', inject(function(filtersService){
urlServiceMock = {
getPathName: function(){
return "test";
}
};
expect(filtersService.getFilters()).not.toBe("something");
}));
});
But this doesn't seem to work. I can't overload the urlServiceMock before it is actually run. I could change the 'beforeEach' each so getPathName returns "test", but then I would not be able to test scenarious where the urls isn't equal to test.

You can spy on a service in your test suite to change the behavior:
spyOn(yourService, 'methodName');
There is a great example on jsFiddle on how to implement a spy: http://jsfiddle.net/robinroestenburg/aDwva/
That should allow you get it working correctly.

Related

Jasmine Tests in a Durandal App

Scenario
I am in the process of writing a number of jasmine tests for a Durandal based app that I am in the process of writing. The Durandal documentation suggests that the way to write tests is like
ViewModel
define([
'knockout',
'plugins/router',
'services/unitofwork',
'services/logger',
'services/errorhandler',
'services/config'
],
function (ko, router, unitofwork, logger, errorhandler, config) {
var uow = unitofwork.create();
var searchTerm = ko.observable();
var results = ko.observableArray([]);
var search = function () {
uow.myySearch(searchTerm).then(function (data) {
results(data);
logger.log(data.length + ' records found', '', 'myViewModel', true);
});
};
var vm = {
search : search,
searchTerm : searchTerm,
results : results
};
});
Test
define(['viewmodels/myViewModel'], function (myViewModel) {
describe('Stuff im testing', function(){
it('returns true', function () {
expect(true).toBe(true);
});
});
});
and for most of my tests this works great.
Problem
How do I mock/stub/fake a module that has been passed into ViewModel. For instance the UnitOfWork module so that it always returns a standard set of data.
For unit testing check out https://github.com/iammerrick/Squire.js/ a dependency mocker for requirejs. Another technique using require context is described in How can I mock dependencies for unit testing in RequireJS?.
For integration testing you might look into something like http://saucelabs.com (selenium based).
For some grunt tasks that helps setting up unit tests in phantomjs|browser see https://github.com/RainerAtSpirit/HTMLStarterKitPro (Disclaimer: I'm the maintainer of the repo). I'd love to see some mockup integration, so send a pull request if you feel inclined.
Check this out
https://github.com/danyg/jasmine-durandal
this is a library that I'm working on, in a few days will have the ability to test widgets too.

Angularjs - how to correctly replace service dependency with a mock

I'm using yeoman generator created app, and doing my tests in karma.
I have reusable mock objects for every of my service.
How do i correctly replace specific service dependcy with a mock, so i could then use jasmine to spy upon methods
So far i have done like this:
My service:
angular.module('ql')
.service('loginService', ['$http','API','authService', function ($http, API, authService) {
return {
//service implementation
}]);
Mock of authService:
'use strict';
//lets mock http auth service, so it would be spied upon.
ql.mock.$authServiceMockProvider = function() {
this.$get = function() {
var $service = {
loginConfirmed: function() { }
};
return $service;
};
};
//and register it.
angular.module('qlMock').provider({
$authServiceMock: ql.mock.$authServiceMockProvider
});
And my test:
'use strict';
describe('When i call login method()', function () {
// load the service's module
beforeEach(module('ql'));
beforeEach(angular.mock.module('qlMock'));
// instantiate service
var loginService,
authService,
$httpBackend;
beforeEach(function() {
// replace auth service with a mock.
// this seems kind of dirty... is there a bettery way?
module(function($provide, $injector){
authService = $injector.get('$authServiceMockProvider').$get();
$provide.value('authService', authService);
});
//actually get the loginService
/*jshint camelcase: false */
inject(function(_loginService_, _$httpBackend_) {
loginService = _loginService_;
$httpBackend =_$httpBackend_;
});
//http auth module method, that should be call only on success scenarios
spyOn(authService, 'loginConfirmed').andCallThrough();
});
it('it should do something', function () {
//actual test logic
});
});
What i do not like is the line:
authService = $injector.get('$authServiceMockProvider').$get();
I would like to simply somehow get the authServiceMock (without getting provider, and calling et method) and then inject it into loginService.
I know i could call my $authServiceMock simply authService, and provide it as a mock, so that it would always override my default implementation, but i do not want to do this.
I know this is late but maybe it will help someone who happen upon this post.
Mocking a service in Jasmine is quite simple using Angular's $provide service. The trick is to use $provide to swap out a service implementation before injecting the service.
For example let's say we are testing a service that makes use of the $location service to get information about the current URL.
// load the service's module under test
beforeEach(module('myExampleModule'));
// mock out $location with a fake one
beforeEach(module(function ($provide) {
//create mock impl
var mockLocation = {
path: function(){
return '/somewhere'
}
}
$provide.value('$location', mockLocation); // use $provide to swap the real $location with our mock
}));
var $location;
// inject dependencies ($location will be our mocked $location)
beforeEach(inject(function (_$location_) {
$location = _$location_;
}));
it('should return mock url', function(){
var path = $location.path();
expect(path).toBe('/somewhere'); //Assert that $location.path() returns '/somewhere'
});
I think I would simply use an angular service decorator to mock or totally replace your service for tests. Here is an example
I have never unit tested a service in a service, not yet anyways but our authertication/login stuff is coming up soon.
As you are unit testing the loginService you are only interested in the way the service interacts with the data it is given by the AuthService and not that the AuthService is working correctly. Which is what you have set up in the mock.
I think this would be my approach: (inside the parent describe)
var
loginService,
authService
AUTH_DATA
;
beforeEach(function() {
module('ql');
// I am assuming this is the global app module so both services live here? If not include this module as well
});
beforeEach(inject(function (_authService_, _loginService_) {
authService = _authService_;
loginService = _loginService_;
//Now with the spy setup you intercept the calls to the service and you choose what data to return, based on the unit test. Now your LoginService can simply resond to the data it is give from the login service
}));
it('it should do something', function () {
spyOn(authService, 'loginConfirmed').andReturn(AUTH_DATA);
loginService.confirmLogin(); //Dont know your actual API but a contrived guess
expect('something to happen in loginservice when AUTH_DATA is returned').toBe('Something else')
});

How do you mock a service in AngularJS when unit testing with jasmine?

Let's say I have a service shop that depends on two stateful services schedule and warehouse. How do I inject different versions of schedule and warehose into shop for unit testing?
Here's my service:
angular.module('myModule').service('shop', function(schedule, warehouse) {
return {
canSellSweets : function(numRequiredSweets){
return schedule.isShopOpen()
&& (warehouse.numAvailableSweets() > numRequiredSweets);
}
}
});
Here are my mocks:
var mockSchedule = {
isShopOpen : function() {return true}
}
var mockWarehouse = {
numAvailableSweets: function(){return 10};
}
Here are my tests:
expect(shop.canSellSweets(5)).toBe(true);
expect(shop.canSellSweets(20)).toBe(false);
beforeEach(function () {
module(function ($provide) {
$provide.value('schedule', mockSchedule);
});
});
Module is a function provided by the angular-mocks module. If you pass in a string argument a module with the corresponding name is loaded and all providers, controllers, services, etc are available for the spec. Generally they are loaded using the inject function. If you pass in a callback function it will be invoked using Angular's $injector service. This service then looks at the arguments passed to the callback function and tries to infer what dependencies should be passed into the callback.
Improving upon Atilla's answer and in direct answer to KevSheedy's comment, in the context of module('myApplicationModule') you would do the following:
beforeEach(module('myApplicationModule', function ($provide) {
$provide.value('schedule', mockSchedule);
}));
With CoffeeScript I run in some issues so I use null at the end:
beforeEach ->
module ($provide) ->
$provide.value 'someService',
mockyStuff:
value : 'AWESOME'
null
You can look here for more info
https://docs.angularjs.org/guide/services#unit-testing
You want to utilize the $provide service. In your case
$provide.value('schedule', mockSchedule);
As you are using jasmine, there is an alternative way to mock the calls with jasmine's spies (https://jasmine.github.io/2.0/introduction.html#section-Spies).
Using these you can be targeted with your function calls, and allow call throughs to the original object if required. It avoids clogging up the top of your test file with $provide and mock implementations.
In the beforeEach of your test I would have something like:
var mySchedule, myWarehouse;
beforeEach(inject(function(schedule, warehouse) {
mySchedule = schedule;
myWarehouse = warehouse;
spyOn(mySchedule, 'isShopOpen').and.callFake(function() {
return true;
});
spyOn(myWarehouse, 'numAvailableSweets').and.callFake(function() {
return 10;
});
}));
and this should work in similar fashion to the $provide mechanism, noting you have to provide local instances of the injected variables to spy on.
I recently released ngImprovedTesting module that should make mock testing in AngularJS way easier.
In your example you would only have to replace in your Jasmine test the ...
beforeEach(module('myModule'));
... with ...
beforeEach(ModuleBuilder.forModule('myModule').serviceWithMocks('shop').build());
For more information about ngImprovedTesting check out its introductory blog post:
http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/
It is simpler to put the mock on the module like this:
beforeEach(function () {
module('myApp');
module({
schedule: mockSchedule,
warehouse: mockWarehouse
}
});
});
you can use injection to get reference to these mocks for pre test manipulations :
var mockSchedule;
var mockWarehouse;
beforeEach(inject(function (_schedule_, _warehouse_) {
mockSchedule = _schedule_;
mockWarehouse = _warehouse_;
}));
I hope my answer is not that useless, but you can mock services by $provide.service
beforeEach(() => {
angular.mock.module(
'yourModule',
($provide) => {
$provide.service('yourService', function() {
return something;
});
}
);
});

AngularJS - Unit testing file uploads

As you know, inside unit tests it's built-in angularjs feature to mock XHR requests with $httpBackend - this is nice and helpful while writing unit tests.
Recently, I met with need of mocking XHR in case of file upload and discovered some problems.
Consider following code:
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", uploadProgress(event), false);
xhr.addEventListener("load", uploadComplete(event), false);
xhr.addEventListener("error", uploadError(event), false);
xhr.addEventListener("abort", uploadAbort(event), false);
xhr.open("POST", 'some url');
xhr.send(someData);
What I want to do is to do unit testing of such a code with mocking of XHR requests, but it's not possible do it because there is no $http service used here.
I tried this (and it's working and could be mocked with $httpBackend):
$http({
method: 'POST',
url: 'some url',
data: someData,
headers: {'Content-Type': undefined},
transformRequest: angular.identity})
.then(successCallback, errorCallback);
But in this case I don't know how to implement 'progress' callback and 'abort' callback (they are essential and required in case I am working on now).
I've seen information that latest Angular supports progress callback for promises (not sure though whether it's integrated with $http service), but what about abort callback?
Any ideas or maybe your met with something similar before?
If the $http service doesn't give you everything you need, you can still unit test the first block of code. First of all, change your code to use Angular's $window service. This is just a wrapper service, but it allows you to mock the object in your tests. So, you'll want to do this:
var xhr = new $window.XMLHttpRequest();
Then in your tests, just mock it and use spies.
$window.XMLHttpRequest= angular.noop;
addEventListenerSpy = jasmine.createSpy("addEventListener");
openSpy = jasmine.createSpy("open");
sendSpy = jasmine.createSpy("send");
xhrObj = {
upload:
{
addEventListener: addEventListenerSpy
},
addEventListener: addEventListenerSpy,
open: openSpy,
send: sendSpy
};
spyOn($window, "XMLHttpRequest").andReturn(xhrObj);
From there, you can make the different spies return whatever you want for the different tests.
You should mock $http and control any deferred, as you want more control over your test. Basically, mock $http provider and serve a custom implementation that exposes its deferred, then play with it.
You should not worry whether $http is working right or not, because it is supposed to, and is already tested. So you have to mock it and only worry testing your part of the code.
You should go something like this:
describe('Testing a Hello World controller', function() {
beforeEach(module(function($provide) {
$provide.provider('$http', function() {
this.$get = function($q) {
return function() {
var deferred = $q.defer(),
promise = deferred.promise;
promise.$$deferred = deferred;
return promise;
}
};
});
}));
it('should answer to fail callback', inject(function(yourService, $rootScope) {
var spyOk = jasmine.createSpy('okListener'),
spyAbort = jasmine.createSpy('abortListener'),
spyProgress = jasmine.createSpy('progressListener');
var promise = yourService.upload('a-file');
promise.then(spyOk, spyAbort, spyProgress);
promise.$$deferred.reject('something went wrong');
$rootScope.$apply();
expect(spyAbort).toHaveBeenCalledWith('something went wrong');
}));
});
And your service is simply:
app.service('yourService', function($http) {
return {
upload: function(file) {
// do something and
return $http({...});
}
};
});
Just note that promises notification is only available in the latest RC release. So, if you can't use it, just elaborate a little more the example and mock the XHR events and so.
Also note that you should preferably have one test case for each of the callbacks (fail, success and progress), in order to follow KISS principle.

Sinon.js and Backbone - Testing for view method invocation

Im working on a test suite for an existing Backbone application using Jasmine and Sinon and I am testing that my router performs the correct actions on a certain route. Here's the actual route function:
favourites: function()
{
//Dont re-initialize the favourites view as there is no need.
//Instead, just render the favourite movies
if ( ! this.favMoviesView)
{
this.favMoviesView = new cinephile.Views.FavouriteMoviesView({
collection: cinephile.favouriteMovies
});
}
else
{
this.favMoviesView.renderFavourites();
}
$('#content').html(this.favMoviesView.el);
},
In my test suite I want to assert that when navigating to to the favourites route this.favMoviesView will be created once and then, if it exists will not re-initialize but instead just call this.favMoviesView.renderFavourites() which is a method that iterates over the view's collection.
Here's my test spec:
describe('cinephile.Routers.CinephileRouter', function () {
beforeEach(function () {
this.router = new cinephile.Routers.CinephileRouter();
this.routeSpy = sinon.spy();
try
{
Backbone.history.start({ silent : true });
}
catch(e) {}
this.router.navigate('elsewhere');
this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView')
.returns(new Backbone.View());
});
afterEach(function () {
this.favouritesViewStub.restore();
});
describe('Favourites Route', function() {
it('should load the favourites on /favourites', function () {
this.router.bind('route:favourites', this.routeSpy);
this.router.navigate('favourites', true);
expect(this.routeSpy.calledOnce).toBeTruthy();
expect(this.routeSpy.calledWith()).toBeTruthy();
});
it('creates a favourites view if one doesn\'t exist', function () {
this.router.favourites();
expect(this.favouritesViewStub.calledOnce).toBeTruthy();
});
it('Reuses the favourites view if one does exist and reset it\'s collection', function () {
this.router.favourites();
this.router.favourites();
expect(this.favouritesViewStub.calledOnce).toBeTruthy();
expect(this.favouritesViewStub.renderFavourites).toHaveBeenCalledTwice();
});
});
});
My first two tests pass and I believe them to correctly describe the favourites method in my router. The third test is the the one giving me problems. As I understand it, because I am testing my router and NOT the FavouriteMoviesView I should be stubbing out the view to keep the test isolated. If that is the correct assumption, my issue becomes that the stub won't have a renderFavourites method as it is a stubbed out Backbone.View().
How can I fix this particular problem and if you are so inclined, I believe I'm missing something conceptual so feel free to explain what it is that I'm not understanding.
Cheers.
You problem is that you want to mock something inside a mock function. What I would suggest is that instead of this...
this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView').returns(new Backbone.View());
...have this:
var StubView = Backbone.View.extend({
renderFavourites: sinon.stub()
});
this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView').returns(new StubView());
Now your View "constructor" will return a StubView, which has the method you are calling stubbed out. So this Backbone View with the stubbed out method will be placed in the router.favMoviesView -property. The favouritesViewStub property still contains just the "constructor" -function, so you can't access this stubbed method from there. This is why you haveto change this from the last test:
expect(this.favouritesViewStub.renderFavourites).toHaveBeenCalledTwice();
to something like this:
expect(this.router.favMoviesView.renderFavourites).toHaveBeenCalledTwice();
This way you will actually check if the router's copy of the view has had the method called twice.
Hope this works for you, comment if it doesn't! I didn't test this out, so there could be some problems, but I'm sure the logic behind works.