Search code examples
angularjsunit-testingjasminekarma-jasmineangularjs-factory

Unit test spyOn is never called angular factory


I am trying to write a unit test for a angular factory:

define([], function () {
    'use strict';

    var factoryName = 'UserFactory';
    var components = ['ProductFactory', '$state'];

    var factory = function (ProductFactory, $state) {

        var viewState = function(view, data) {
            if(data.access[view] === true) {
                return;
            }
            else {                
                $state.go('notAuth');
            }
        };

        return {
            viewState: viewState
        };
    };

    components.push(factory);

    return {
        name: factoryName,
        component: components
    };
});

I have the following unit test:

define(['require', 'angular-mocks'], function (require) {
    'use strict';

    var angular = require('angular');

    describe('<-- UserFactory Spec ------>', function () {

        var scope, $httpBackend, userFactory;

        beforeEach(angular.mock.module('UserFactory'));

        beforeEach(inject(function (_$rootScope_, _UserFactory_) {
            scope = _$rootScope_.$new();
            userFactory = _UserFactory_;
            spyOn(userFactory, 'viewState').and.returnValue({firstName:'Joe',lastName:'Smith',access:'read'});
            scope.$apply();
        }));


        it('method viewState() was called', function () {
            expect(userFactory.viewState).toHaveBeenCalled();
        });
    });
});

However i see the following error:

Expected spy viewState to have been called.

Solution

  • If you are trying to test the implementation of the viewState function, you will need to call it in your test. You also don't need to test it was called (because the source code doesn't test it).

    You don't need to mock it out, that defeats the purpose of testing it. This is because a mock does not call through to the actual implementation, unless you tell it to but you don't need that here.

    Your test should look something like this:

    describe('<-- UserFactory Spec ------>', function () {
    
        var userFactory;
        var $state = {
            go: function(){}
        };
    
        beforeEach(angular.mock.module('UserFactory'));
    
        // Set the $state injectable to be our mocked out $state (only for this test)
        beforeEach(module(function ($provide) {
            $provide.value('$state', $state);
        }));
    
        beforeEach(inject(function (_UserFactory_) {
            userFactory = _UserFactory_;
    
            // Mock out the $state.go method
            spyOn($state, 'go'); 
        }));
    
    
        it('should redirect to the notAuth state', function () {
            // Call the method we are testing
            userFactory.viewState(/** pass in some conditions to make it call $state.go**/);
            expect($state.go).toHaveBeenCalledWith('notAuth');
        });
    });
    

    I have done a few things here:

    • Removed $scope, you don't need it here.
    • Mocked out the $state.go function - so we can test it was called.
    • Called userFactory.viewState() - so we can test the function.
    • Removed your mock of viewState() - so we can test the actual implementation.

    To test that it is calling this method correctly, you can use some console.logs.

    var viewState = function(view, data) {
        console.log('View ', view);
        console.log('Data ', data);
        console.log(data.access[view]);
        console.log(data.access[view] === true);
        if(data.access[view] === true) {
            return;
        }
        else {                
            $state.go('notAuth');
        }
    };
    

    Now just check that all the console logs are what you expect.


    P.S I can't test this code so it might not be 100%, but this is the general gist.