SilentDev
SilentDev

Reputation: 22777

Jasmine controller and mock service not being injected into the unit tests

This is my test_sp.js file:

describe('Controller: MainCtrl', function() {
    var ctrl, mockBaseService;

    beforeEach(function() {

        mockBaseService = {
            cerrorMessages: 'whatever',
            sp: "[{'spName': 'Test'}]",
            // Calls back if errors.
            add: jasmine.createSpyObj('BaseService.add', ['sp']),
            logout: jasmine.createSpy('BaseService.logout'),
            // Calls back if errors.
            fetch: jasmine.createSpyObj('BaseService.fetch', ['selfsp'])
        };

        mockBaseService.add.sp.and.callFake(function(something, cb) {
            cb(); // execute the callback immediately
        });

        mockBaseService.fetch.selfsp.and.callFake(function(something, cb) {
            cb(); // execute the callback immediately
        });

        module('SpPageApp');

        // Let ctrl = MainCtrl and override BaseService in MainCtrl to be
        // the mockBaseService above.
        inject(function($controller) {
            ctrl = $controller('MainCtrl', {
                BaseService: mockBaseService
            });
        });
    });

    it('should have an add function', function() {
        expect(ctrl.add).toBeDefined();
    });
});

And this is my karma config file's file array:

files: [
  '../angular.js',
  'node_modules/angular-mocks/angular-mocks.js',
  '../w.js',
  '../sp.js',
  '../s.js',
  '../base.js',
  'tests/test_sp.js',
],

Lastly, this is my sp.js which has the SpPageApp module:

angular.module("SpPageApp", ["BaseApp"])
    .controller("MainCtrl", ["$http", "$window", "BaseService", function($http, $window, BaseService) {

        var self = this;

        BaseService.fetch.selfsp(function() {
            self.sp = BaseService.sp;
            self.cerrorMessages = BaseService.cerrorMessages;
        });

        self.add = function() {
            BaseService.add.sp(self.sp, function() {
                self.cerrorMessages = BaseService.cerrorMessages;
            });
        };

        self.logoutUser = function() {
            BaseService.logout();
        };

    }]);

The I do karma start to test the code, I get an error saying this:

Chromium 47.0.2526 (Ubuntu 0.0.0) Controller: MainsCtrl should have an add function FAILED
    TypeError: Cannot read property 'and' of undefined
        at Object.<anonymous> (/home/a/Documents/CMS/CMSApp/static/js/karma/tests/test_sp.js:20:45)
    TypeError: Cannot read property 'add' of undefined
        at Object.<anonymous> (/home/a/Documents/CMS/CMSApp/static/js/karma/tests/test_sp.js:36:20)
Chromium 47.0.2526 (Ubuntu 0.0.0): Executed 1 of 1 (1 FAILED) (0 secs / 0.006 secChromium 47.0.2526 (Ubuntu 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.034 secs / 0.006 secs)

The error says it cannot read the property add of defined, and points to this line:

expect(ctrl.add).toBeDefined();

and that it cannot read the property and of undefined, and points to this line:

mockBaseService.fetch.selfsp.and.callFake(function(something, cb) {

How come Jasmine is saying both ctrl and mockBaseService.fetch.selfsp are undefined when they are defined in the code?

Edit: For what it's worth, w.js, sp.js and s.js (which karma is loading) all have controllers called MainCtrl but each controller is in it's own file and AngularJS module (and in test_sp.js, I only load the SpPageApp module).

Upvotes: 1

Views: 1542

Answers (1)

David L
David L

Reputation: 33873

As mentioned in my comments, you can instead mock the service directly and avoid using spies. In addition, you can tell the $injector to use your mock service instead of the real implementation, allowing you to inject the mock directly when resolving the service, which replaces your normal implementation.

Assuming that your BaseService exists in the SpPageApp module:

mockBaseService = {
    cerrorMessages: 'whatever',
    sp: "[{'spName': 'Test'}]",
    add: function (something, cb) { cb() },
    ...
};

module('BaseApp', function($provide) {
    $provide.value('BaseService', mockBaseService);
});

module('SpPageApp');

inject(function($controller) {
    ctrl = $controller('MainCtrl', {
    });
});

To test it, as mentioned in the comments, you would still want to use a spy to make sure that it was successfully called.

it('should have an add function', function() {
    spyOn(mockBaseService, 'add');        
    ctrl.add();
    expect(mockBaseService.add).toHaveBeenCalled();
});

Upvotes: 2

Related Questions