如何在 JavaScript 单元测试中模拟 localStorage?

IT技术 javascript unit-testing mocking local-storage sinon
2021-03-06 00:47:17

有没有可以模拟的库localStorage

我一直在使用Sinon.JS进行大多数其他 javascript模拟,并且发现它真的很棒。

我的初始测试表明 localStorage 拒绝在 Firefox (sadface) 中分配,所以我可能需要对此进行某种破解:/

我现在的选择(如我所见)如下:

  1. 创建我所有代码使用的包装函数并模拟它们
  2. 为 localStorage 创建某种(可能很复杂)状态管理(测试前的快照 localStorage,在清理恢复快照中)。
  3. ??????

您如何看待这些方法,您认为还有其他更好的方法来解决这个问题吗?无论哪种方式,我都会把我最终在 github 上制作的结果“库”放在开源的优点上。

6个回答

这是用 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 = {}));
});

如果要在所有测试中模拟本地存储,请在测试beforeEach()的全局范围内声明上面显示函数(通常的位置是specHelper.js脚本)。

+1 - 你也可以用 sinon 来做到这一点。关键是为什么要费心去模拟整个 localStorage 对象,只需模拟您感兴趣的方法(getItem 和/或 setItem)。
2021-04-20 00:47:17
尝试监视 window.localStorage
2021-04-27 00:47:17
andCallFake改为and.callFake在茉莉花 2.+
2021-04-28 00:47:17
我得到一个ReferenceError: localStorage is not defined(使用 FB Jest 和 npm 运行测试)……有什么想法可以解决吗?
2021-05-02 00:47:17
注意:Firefox 中的此解决方案似乎存在问题:github.com/pivotal/jasmine/issues/299
2021-05-09 00:47:17

只需根据您的需要模拟全局 localStorage / sessionStorage (它们具有相同的 API)。
例如:

 // 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;
      }
    };
  }

然后你实际做的事情是这样的:

// mock the localStorage
window.localStorage = storageMock();
// mock the sessionStorage
window.sessionStorage = storageMock();
About setItem,它应该是storage[key] = ${value}`` 而不是storage[key] = value || ''因为你可以这样做sessionStorage.setItem('foo', undefined),它会将 undefined (或 null)保存为一个字符串。
2021-05-09 00:47:17
@a8m 将节点更新到 10.15.1 后出现错误TypeError: Cannot set property localStorage of #<Window> which has only a getter,知道如何解决此问题吗?
2021-05-10 00:47:17
是的,不幸的是这不再起作用,但我也认为这storage[key] || null是不正确的。如果storage[key] === 0它会返回null我认为你可以做到return key in storage ? storage[key] : null
2021-05-11 00:47:17
刚刚在 SO 上使用了这个!像魅力一样工作 - 只需在真实服务器上将 localStor 改回 localStoragefunction storageMock() { var 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) { var keys = Object.keys(storage); return keys[i] || null; } }; } window.localStor = storageMock();
2021-05-14 00:47:17
截至 2016 年,这似乎不适用于现代浏览器(检查 Chrome 和 Firefox);localStorage作为一个整体覆盖是不可能的。
2021-05-17 00:47:17

还要考虑在对象的构造函数中注入依赖项的选项。

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)

根据模拟和单元测试,我喜欢避免测试存储实现。例如,在设置项目等后检查存储长度是否增加没有意义。

由于替换真实 localStorage 对象上的方法显然不可靠,因此请使用“哑”模拟存储并根据需要存根各个方法,例如:

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');
我意识到我已经有一段时间没有看过这个问题了——但这实际上是我最终做的。
2021-04-22 00:47:17
这是唯一值得的解决方案,因为它没有那么高的及时中断风险。
2021-05-02 00:47:17

当前的解决方案在 Firefox 中不起作用。这是因为 localStorage 由 html 规范定义为不可修改。但是,您可以通过直接访问 localStorage 的原型来解决此问题。

跨浏览器的解决方案是模拟对象,Storage.prototype例如

而不是spyOn(localStorage, 'setItem')使用

spyOn(Storage.prototype, 'setItem')
spyOn(Storage.prototype, 'getItem')

取自bzbarskyteogeos的回复https://github.com/jasmine/jasmine/issues/299

你的评论应该得到更多的喜欢。谢谢!
2021-04-21 00:47:17
同意,这也是解决 Prebid PR 问题的最佳答案!
2021-05-09 00:47:17

这就是我所做的...

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,
});