How to mock localStorage in JavaScript unit tests?

前端 未结 14 1546
南笙
南笙 2020-11-29 18:46

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 gr

相关标签:
14条回答
  • 2020-11-29 19:12

    Unfortunately, the only way we can mock the localStorage object in a test scenario is to change the code we're testing. You have to wrap your code in an anonymous function (which you should be doing anyway) and use "dependency injection" to pass in a reference to the window object. Something like:

    (function (window) {
       // Your code
    }(window.mockWindow || window));
    

    Then, inside of your test, you can specify:

    window.mockWindow = { localStorage: { ... } };
    
    0 讨论(0)
  • 2020-11-29 19:13

    Here is a simple way to mock it with Jasmine:

    beforeEach(function () {
      var store = {};
    
      spyOn(localStorage, 'getItem').andCallFake(function (key) {
        return store[key];
      });
      spyOn(localStorage, 'setItem').andCallFake(function (key, value) {
        return store[key] = value + '';
      });
      spyOn(localStorage, 'clear').andCallFake(function () {
          store = {};
      });
    });
    

    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).

    0 讨论(0)
  • 2020-11-29 19:19

    Here is an exemple using sinon spy and mock:

    // window.localStorage.setItem
    var spy = sinon.spy(window.localStorage, "setItem");
    
    // You can use this in your assertions
    spy.calledWith(aKey, aValue)
    
    // Reset localStorage.setItem method    
    spy.reset();
    
    
    
    // window.localStorage.getItem
    var stub = sinon.stub(window.localStorage, "getItem");
    stub.returns(aValue);
    
    // You can use this in your assertions
    stub.calledWith(aKey)
    
    // Reset localStorage.getItem method
    stub.reset();
    
    0 讨论(0)
  • 2020-11-29 19:20

    I decided to reiterate my comment to Pumbaa80's answer as separate answer so that it'll be easier to reuse it as a library.

    I took Pumbaa80's code, refined it a bit, added tests and published it as an npm module here: https://www.npmjs.com/package/mock-local-storage.

    Here is a source code: https://github.com/letsrock-today/mock-local-storage/blob/master/src/mock-localstorage.js

    Some tests: https://github.com/letsrock-today/mock-local-storage/blob/master/test/mock-localstorage.js

    Module creates mock localStorage and sessionStorage on the global object (window or global, which of them is defined).

    In my other project's tests I required it with mocha as this: mocha -r mock-local-storage to make global definitions available for all code under test.

    Basically, code looks like follows:

    (function (glob) {
    
        function createStorage() {
            let s = {},
                noopCallback = () => {},
                _itemInsertionCallback = noopCallback;
    
            Object.defineProperty(s, 'setItem', {
                get: () => {
                    return (k, v) => {
                        k = k + '';
                        _itemInsertionCallback(s.length);
                        s[k] = v + '';
                    };
                }
            });
            Object.defineProperty(s, 'getItem', {
                // ...
            });
            Object.defineProperty(s, 'removeItem', {
                // ...
            });
            Object.defineProperty(s, 'clear', {
                // ...
            });
            Object.defineProperty(s, 'length', {
                get: () => {
                    return Object.keys(s).length;
                }
            });
            Object.defineProperty(s, "key", {
                // ...
            });
            Object.defineProperty(s, 'itemInsertionCallback', {
                get: () => {
                    return _itemInsertionCallback;
                },
                set: v => {
                    if (!v || typeof v != 'function') {
                        v = noopCallback;
                    }
                    _itemInsertionCallback = v;
                }
            });
            return s;
        }
    
        glob.localStorage = createStorage();
        glob.sessionStorage = createStorage();
    }(typeof window !== 'undefined' ? window : global));
    

    Note that all methods added via Object.defineProperty so that them won't be iterated, accessed or removed as regular items and won't count in length. Also I added a way to register callback which is called when an item is about to be put into object. This callback may be used to emulate quota exceeded error in tests.

    0 讨论(0)
  • 2020-11-29 19:21

    This is what I do...

    var mock = (function() {
      var store = {};
      return {
        getItem: function(key) {
          return store[key];
        },
        setItem: function(key, value) {
          store[key] = value.toString();
        },
        clear: function() {
          store = {};
        }
      };
    })();
    
    Object.defineProperty(window, 'localStorage', { 
      value: mock,
    });
    
    0 讨论(0)
  • 2020-11-29 19:21

    You don't have to pass the storage object to each method that uses it. Instead, you can use a configuration parameter for any module that touches the storage adapter.

    Your old module

    // hard to test !
    export const someFunction (x) {
      window.localStorage.setItem('foo', x)
    }
    
    // hard to test !
    export const anotherFunction () {
      return window.localStorage.getItem('foo')
    }
    

    Your new module with config "wrapper" function

    export default function (storage) {
      return {
        someFunction (x) {
          storage.setItem('foo', x)
        }
        anotherFunction () {
          storage.getItem('foo')
        }
      }
    }
    

    When you use the module in testing code

    // import mock storage adapater
    const MockStorage = require('./mock-storage')
    
    // create a new mock storage instance
    const mock = new MockStorage()
    
    // pass mock storage instance as configuration argument to your module
    const myModule = require('./my-module')(mock)
    
    // reset before each test
    beforeEach(function() {
      mock.clear()
    })
    
    // your tests
    it('should set foo', function() {
      myModule.someFunction('bar')
      assert.equal(mock.getItem('foo'), 'bar')
    })
    
    it('should get foo', function() {
      mock.setItem('foo', 'bar')
      assert.equal(myModule.anotherFunction(), 'bar')
    })
    

    The MockStorage class might look like this

    export default class MockStorage {
      constructor () {
        this.storage = new Map()
      }
      setItem (key, value) {
        this.storage.set(key, value)
      }
      getItem (key) {
        return this.storage.get(key)
      }
      removeItem (key) {
        this.storage.delete(key)
      }
      clear () {
        this.constructor()
      }
    }
    

    When using your module in production code, instead pass the real localStorage adapter

    const myModule = require('./my-module')(window.localStorage)
    
    0 讨论(0)
提交回复
热议问题