71

I'm attempting to unit test controller code inside a module that takes other modules as dependencies, but haven't been able to figure out how to mock them properly.

I'm using the Jasmine Framework and running my tests with Karma (Testacular).

Module Code

var app = angular.module('events', ['af.widgets', 'angular-table']);

app.controller('eventsCtrl', function([dependencies]){
    $scope.events = [];
    ...
});

Spec Code

describe('events module', function(){
    var $scope,
        ctrl;

    beforeEach(function(){
        angular.mock.module('af.widgets', []);
        angular.mock.module('angular-table', []);
        module('events', ['af.widgets', 'angular-table']);
    });

    beforeEach(inject(function($rootScope, $controller){
        $scope = $rootScope.new();
        ctrl = $controller('NameCtrl', {
            $scope: $scope,
        });
    }));

    it('should have an empty events array', function(){
        expect($scope.events).toBe([]);
    })
});

The error I'm getting is Karma is "no module af.widgets", so obviously I'm not mocking the module dependencies right. Any hints?

Mark
  • 3,143
  • 3
  • 31
  • 56
fscof
  • 1,523
  • 2
  • 17
  • 27

3 Answers3

65

If you want to mock a module that declare one or more services I have used this code:

beforeEach(function(){
    module('moduleToMock');
    module(function ($provide) {
        $provide.value('yourService', serviceMock);
    });
});

This is useful if the service you want to mock is also a service that you want to unit test (in another jasmine describe). The solution proposed by fscof is fine but you cannot create a unit test for the angular-table module.

Davide Icardi
  • 10,707
  • 7
  • 51
  • 67
  • 4
    This is right way. Because you often don't want to use one mock for all tests. Se also here: http://stackoverflow.com/a/18756347/1105860 – Roman Bekkiev Dec 13 '14 at 16:40
  • And what if I want to put `serviceMock` in another file, without declaring global variables ? – Cyril CHAPON Sep 08 '15 at 12:28
  • 1
    I prefer this method for my own modules, however I don't want to write any unit test for 3rd party modules. In that case I prefer @fscof his solution. Both are correct, depending on context :-) – Pim Hazebroek Jan 28 '16 at 11:06
  • What if I want to return promise from method of serviceMock object. I need $q, right? How to get that inside module function. I'm unable to get that.. Can you help me? – Jay Shukla Feb 02 '16 at 11:05
46

Here's what I figured out:

I wasn't loading any 'angular-table' modules in my karma.conf.js file, hence the error. This was intentional at first as I wanted to test the 'events' module without the actual table module.

I was able to easily mock the 'angular-table' module by creating a new file in my test folder called 'mocks/angular-table.js' and added the following code:

/mocks/angular-table.js

'use-strict';
angular.module('angular-table', []);

I added this file to my karma.conf.js file, along with the real 'events' module I wanted to test:

karma.conf.js

...
files = [
    JASMINE,
    JASMINE_ADAPTER,
    'scripts/libs/angular.js',
    'scripts/libs/angular-mocks.js',
    'scripts/events.js', // this is the real module.
    'scripts/mocks/*.js', //loads all custom mocks.
    'scripts/specs/*.spec.js' // loads my spec file.
] 
...

Finally in my spec file, I was able to add both modules by calling them separately in a beforeEach block:

specs/events.spec.js

beforeEach(function(){
    module('angular-table');
    module('events');
});

I got the idea to structure my files in this way from this post

fscof
  • 1,523
  • 2
  • 17
  • 27
  • 2
    Aren't you not mocking the 'angular-table' module by calling angular.module? I think you want to call module, as angular-mocks attaches it to the global scope? At either rate, thank you as this helped me get my specs up and running. – phillyslick Jan 14 '14 at 17:13
  • "I was able to easily mock the 'angular-table' module by creating a new file in my test folder called 'mocks/angular-table.js' and added the following code: /mocks/angular-table.js " – Priya Ranjan Singh Aug 20 '14 at 15:31
  • @fscof The link is giving 404. – Lucio May 02 '15 at 19:55
3

I recently released ngImprovedTesting that should make mock testing in AngularJS way easier.

In your case just use the following in your Jasmine test:

beforeEach(ModuleBuilder.forModule('events').serviceWithMocks('eventsCtrl').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/

C.Champagne
  • 4,706
  • 2
  • 21
  • 32