How to mock localStorage in JavaScript unit tests?
Are there any libraries out there to mock localStorage
?
I've been using Sinon.JS for most of my other javascript mocking and have found it is really great.
My initial testing shows that localStorage refuses to be assignable in firefox (sadface) so I'll probably need some sort of hack around this :/
My options as of now (as I see) are as follows:
- Create wrapping functions that all my code uses and mock those
- Create some sort of (might be complicated) state management (snapshot localStorage before test, in cleanup restore snapshot) for localStorage.
??????
What do you think of these approaches and do you think there are any other better ways to go about this? Either way I'll put the resulting "library" that I end up making on github for open source goodness.
Solution 1:
Here is a simple way to mock it with Jasmine:
let localStore;
beforeEach(() => {
localStore = {};
spyOn(window.localStorage, 'getItem').and.callFake((key) =>
key in localStore ? localStore[key] : null
);
spyOn(window.localStorage, 'setItem').and.callFake(
(key, value) => (localStore[key] = value + '')
);
spyOn(window.localStorage, 'clear').and.callFake(() => (localStore = {}));
});
If you want to mock the local storage in all your tests, declare the beforeEach()
function shown above in the global scope of your tests (the usual place is a specHelper.js script).
Solution 2:
just mock the global localStorage / sessionStorage (they have the same API) for your needs.
For example:
// Storage Mock
function storageMock() {
let storage = {};
return {
setItem: function(key, value) {
storage[key] = value || '';
},
getItem: function(key) {
return key in storage ? storage[key] : null;
},
removeItem: function(key) {
delete storage[key];
},
get length() {
return Object.keys(storage).length;
},
key: function(i) {
const keys = Object.keys(storage);
return keys[i] || null;
}
};
}
And then what you actually do, is something like that:
// mock the localStorage
window.localStorage = storageMock();
// mock the sessionStorage
window.sessionStorage = storageMock();
Solution 3:
Also consider the option to inject dependencies in an object's constructor function.
var SomeObject(storage) {
this.storge = storage || window.localStorage;
// ...
}
SomeObject.prototype.doSomeStorageRelatedStuff = function() {
var myValue = this.storage.getItem('myKey');
// ...
}
// In src
var myObj = new SomeObject();
// In test
var myObj = new SomeObject(mockStorage)
In line with mocking and unit testing, I like to avoid testing the storage implementation. For instance no point in checking if length of storage increased after you set an item, etc.
Since it is obviously unreliable to replace methods on the real localStorage object, use a "dumb" mockStorage and stub the individual methods as desired, such as:
var mockStorage = {
setItem: function() {},
removeItem: function() {},
key: function() {},
getItem: function() {},
removeItem: function() {},
length: 0
};
// Then in test that needs to know if and how setItem was called
sinon.stub(mockStorage, 'setItem');
var myObj = new SomeObject(mockStorage);
myObj.doSomeStorageRelatedStuff();
expect(mockStorage.setItem).toHaveBeenCalledWith('myKey');
Solution 4:
The current solutions will not work in Firefox. This is because localStorage is defined by the html spec as being not modifiable. You can however get around this by accessing localStorage's prototype directly.
The cross browser solution is to mock the objects on Storage.prototype
e.g.
instead of spyOn(localStorage, 'setItem') use
spyOn(Storage.prototype, 'setItem')
spyOn(Storage.prototype, 'getItem')
taken from bzbarsky and teogeos's replies here https://github.com/jasmine/jasmine/issues/299