How do I deal with localStorage in jest tests?

2019-01-12 21:19发布

问题:

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

回答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] = value.toString();
  }

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

global.localStorage = new LocalStorageMock;



回答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",



回答3:

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



回答4:

A better alternative which handles undefined values (it doesn't have toString()) and returns null if value doesn't exist. Tested this with react v15, redux and redux-auth-wrapper

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

  clear() {
    this.store = {}
  }

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

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

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

global.localStorage = new LocalStorageMock


回答5:

or you just take a mock package like this:

https://www.npmjs.com/package/jest-localstorage-mock

it handles not only the storage functionality but also allows you test if the store was actually called.



回答6:

Riffed off some other answers here to solve it for a project with Typescript. I created a LocalStorageMock like this:

export class LocalStorageMock {

    private store = {}

    clear() {
        this.store = {}
    }

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

    setItem(key: string, value: string) {
        this.store[key] = value
    }

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

Then I created a LocalStorageWrapper class that I use for all access to local storage in the app instead of directly accessing the global local storage variable. Made it easy to set the mock in the wrapper for tests.



回答7:

As @ck4 suggested documentation has clear explanation for using localStorage in jest. However the mock functions were failing to execute any of the localStorage methods.

Below is the detailed example of my react component which make uses of abstract methods for writing and reading data,

//file: storage.js
const key = 'ABC';
export function readFromStore (){
    return JSON.parse(localStorage.getItem(key));
}
export function saveToStore (value) {
    localStorage.setItem(key, JSON.stringify(value));
}

export default { readFromStore, saveToStore };

Error:

TypeError: _setupLocalStorage2.default.setItem is not a function

Fix:
Add below mock function for jest (path: .jest/mocks/setUpStore.js )

let mockStorage = {};

module.exports = window.localStorage = {
  setItem: (key, val) => Object.assign(mockStorage, {[key]: val}),
  getItem: (key) => mockStorage[key],
  clear: () => mockStorage = {}
};

Snippet is referenced from here



回答8:

Currently (Jan '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 here https://github.com/facebook/jest/issues/6798 and here https://github.com/jsdom/jsdom/issues/2318.

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();


标签: jestjs