How do I deal with localStorage in jest tests?

I keep getting "localStorage is not defined" in Jest tests which makes sense but what are my options? Hitting brick walls.


Solution 1:

Great solution from @chiedo

However, we use ES2015 syntax and I felt it was a little cleaner to write it this way.

class LocalStorageMock {
  constructor() {
    this.store = {};
  }

  clear() {
    this.store = {};
  }

  getItem(key) {
    return this.store[key] || null;
  }

  setItem(key, value) {
    this.store[key] = String(value);
  }

  removeItem(key) {
    delete this.store[key];
  }
}

global.localStorage = new LocalStorageMock;

Solution 2:

Figured it out with help from this: https://groups.google.com/forum/#!topic/jestjs/9EPhuNWVYTg

Setup a file with the following contents:

var localStorageMock = (function() {
  var store = {};
  return {
    getItem: function(key) {
      return store[key];
    },
    setItem: function(key, value) {
      store[key] = value.toString();
    },
    clear: function() {
      store = {};
    },
    removeItem: function(key) {
      delete store[key];
    }
  };
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });

Then you add the following line to your package.json under your Jest configs

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",

Solution 3:

Currently (Oct '19) localStorage can not be mocked or spied on by jest as you usually would, and as outlined in the create-react-app docs. This is due to changes made in jsdom. You can read about it in the jest and jsdom issue trackers.

As a workaround, you can spy on the prototype instead:

// does not work:
jest.spyOn(localStorage, "setItem");
localStorage.setItem = jest.fn();

// works:
jest.spyOn(window.localStorage.__proto__, 'setItem');
window.localStorage.__proto__.setItem = jest.fn();

// assertions as usual:
expect(localStorage.setItem).toHaveBeenCalled();

Solution 4:

If using create-react-app, there is a simpler and straightforward solution explained in the documentation.

Create src/setupTests.js and put this in it :

const localStorageMock = {
  getItem: jest.fn(),
  setItem: jest.fn(),
  clear: jest.fn()
};
global.localStorage = localStorageMock;

Tom Mertz contribution in a comment below :

You can then test that your localStorageMock's functions are used by doing something like

expect(localStorage.getItem).toBeCalledWith('token')
// or
expect(localStorage.getItem.mock.calls.length).toBe(1)

inside of your tests if you wanted to make sure it was called. Check out https://facebook.github.io/jest/docs/en/mock-functions.html