TIL: Mocking localStorage and sessionStorage in Angular Unit Tests

Today I learned how to mock localStorage and sessionStorage in a Service’s unit tests in Angular.

(Well, it’s not actually today that I learned this, but as a thing that I use in many projects and I still always forgot. It’s worth taking some notes 📝 so I will never forget this again … which of course, I will)

Here are the reference articles/posts I learned from

Thank you guys!

I created a companion repo for this post on GitHub. You can check the code and try it out at armno/angular-mock-localstorage.

It is based on @angular/cli version 1.0.0-rc.4 and @angular/* version 2.4.10 . The code is also working with Angular 4.0.0-rc.1 .


The TokenService

// token.service.tsimport { Injectable } from ‘@angular/core’;@Injectable()
export class TokenService {
private TOKEN_KEY = ‘id_token’; constructor() { } setAccessToken(token: string) {
localStorage.setItem(this.TOKEN_KEY, token);
}
getAccessToken(): string {
return localStorage.getItem(this.TOKEN_KEY);
}
}

In this Service’s unit tests, we don’t want it to use the real localStorage because our tests should be able to run independently from localStorage. We are only focusing on the Service itself, and not anything else.

This is a generated spec file of TokenService (via angular-cli)

// token.service.spec.tsimport { TestBed, inject } from '@angular/core/testing';import { TokenService } from './token.service';describe('TokenService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [TokenService]
});
});
it('should ...',
inject([TokenService], (service: TokenService) => {
expect(service).toBeTruthy();
}));
});

which I normally adapt a bit (and fix that should ... ) to

import { TestBed, inject } from '@angular/core/testing';import { TokenService } from './token.service';describe('TokenService', () => {
let service: TokenService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [TokenService]
});
service = TestBed.get(TokenService);
});
it('should create the service',
() => {
expect(service).toBeTruthy();
});
});

Creating mock localStorage

beforeEach(() => {
...
let store = {};
const mockLocalStorage = {
getItem: (key: string): string => {
return key in store ? store[key] : null;
},
setItem: (key: string, value: string) => {
store[key] = `${value}`;
},
removeItem: (key: string) => {
delete store[key];
},
clear: () => {
store = {};
}
};

});

Then we can use spyOn method with and.callFake for each mockLocalStorage object’s methods.

beforeEach(() => {
...
let store = {};
const mockLocalStorage = {
getItem: (key: string): string => {
return key in store ? store[key] : null;
},
setItem: (key: string, value: string) => {
store[key] = `${value}`;
},
removeItem: (key: string) => {
delete store[key];
},
clear: () => {
store = {};
}
};
spyOn(localStorage, 'getItem')
.and.callFake(mockLocalStorage.getItem);
spyOn(localStorage, 'setItem')
.and.callFake(mockLocalStorage.setItem);
spyOn(localStorage, 'removeItem')
.and.callFake(mockLocalStorage.removeItem);
spyOn(localStorage, 'clear')
.and.callFake(mockLocalStorage.clear);

});

This basically means: whenever localStorage.getItem is called, instead, call mockLocalStorage.getItem with the same arguments, and so on.

Note that .length property and key() method are not implemented. I personally never have to use them but there should be some examples online on how to also mock them. 😅

And finally, the tests:

describe('setAccessToken', () => {
it('should store the token in localStorage',
() => {
service.setAccessToken('sometoken');
expect(localStorage.getItem('id_token')).toEqual('sometoken');
});
});
describe('getAccessToken', () => {
it('should return stored token from localStorage',
() => {
localStorage.setItem('id_token', 'anothertoken');
expect(service.getAccessToken()).toEqual('anothertoken');
});
});

This is the pattern I’ve been using for all of my Angular projects so far. Of course, feedback is welcome. 🙏

Armno Prommarak 🇹🇭

Written by

a frontend nerd at BUZZWOO!