WitteStier WitteStier - 2 months ago 17
AngularJS Question

cant update jasmine spy

Hi I have a Angular service that uses another service that loads data from the local storage on init.

angular
.module('app')
.factory('localStorage', function ($window)
{
if (!$window.localStorage)
{
// throw Error
}

return $window.localStorage;
});

angular
.module('app')
.factory('session', function (localStorage)
{
var container = JSON.parse(localStorage.getItem('sessionContainer'));

return {
getUser: getUser
};
});


Now i want to test the session service.

describe('SessionService', function ()
{
var service;
var localStorageMock;

// Load the module.
beforeEach(module('appRegistration'));

// Create mocks.
beforeEach(function ()
{
logMock = {};

localStorageMock = jasmine.createSpyObj('localStorageServiceMockSpy', ['setItem', 'getItem']);
localStorageMock.getItem.and.returnValue('{}');

module(function ($provide)
{
$provide.value('localStorage', localStorageMock);
});

inject(function (_session_)
{
service = _session_;
});
});

it('should call `getItem` on the `localStorageService` service', function ()
{
expect(localStorageMock.getItem).toHaveBeenCalledWith('sessionContainer');
});

describe('getUser method', function ()
{
it('should return an empty object when the user is not set', function ()
{
var result = service.getUser();

expect(result).toEqual({});
});

it('should return the user data', function ()
{
// localStorageMock.getItem.and.returnValue('{"user":{"some":"data"}}');

var result = service.getUser();

expect(result).toEqual({some: 'user data'});
});
});

});


As you can see in the
should return the user data
section.
I need a way to update the
container
so
getUser
returns the expected data.

I tried to update the
getItem
spy, but this does not work. The
localStorageMock
is already injected in the
session
service when i want to change the spy.

Any help?

Answer

The most simple way is to have a variable with mocked value that is common for both function scopes:

    var getItemValue;
    beforeEach({
      localStorage: {
        getItem: jasmine.createSpy().and.callFake(function () {
          return getItemValue;
        }),
        setItem: jasmine.createSpy()
      }
    });

    ...
        it('should return the user data', function ()
        {

            getItemValue = '{"user":{"some":"data"}}';

            inject(function (_session_) {
                service = _session_;
            });

            var result = service.getUser();

            expect(result).toEqual({some: 'user data'});
        });

Notice that inject should be moved from beforeEach to it for all specs (the specs that don't involve getItemValue may use shorter syntax, it('...', inject(function (session) { ... }))).

This reveals the flaw in service design that makes it test-unfriendly.

The solution is to make container lazily evaluated, so there is time to mock it after the app was bootstrapped with inject:

.factory('session', function (localStorage)
{
    var containerCache;

    function getUser() {
        ...
        return this.container;
    }
    return {
        get container() {
            return (containerCache === undefined)
                ? (containerCache = JSON.parse(localStorage.getItem('sessionContainer')))
                : containerCache;
        },
        getUser: getUser
    };
});

Additionally, this makes possible to test session.container as well. In this case localStorageMock.getItem spy value may be redefined whenever needed.

Comments