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

Ant*_*ile 90 javascript unit-testing mocking local-storage sinon

那里有没有图书馆要模拟localStorage

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

我的初步测试显示localStorage拒绝在firefox(sadface)中分配,所以我可能需要某种黑客攻击:/

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

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

您如何看待这些方法,您认为还有其他更好的方法吗?无论哪种方式,我都会把最终制作的"库"放在github上,以获得开源的优点.

And*_*rle 120

以下是使用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 = {};
  });
});
Run Code Online (Sandbox Code Playgroud)

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

  • `andCallFake`在jasmine 2. +中更改为`and.callFake` (19认同)
  • 抬头:这个解决方案在Firefox中似乎存在问题:https://github.com/pivotal/jasmine/issues/299 (6认同)
  • 我得到一个`ReferenceError:localStorage没有定义`(使用FB Jest和npm运行测试)...任何想法如何解决? (4认同)

a8m*_*a8m 48

只需模拟全局localStorage/sessionStorage(它们具有相同的API)即可满足您的需求.
例如:

 // Storage Mock
  function 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;
      }
    };
  }
Run Code Online (Sandbox Code Playgroud)

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

// mock the localStorage
window.localStorage = storageMock();
// mock the sessionStorage
window.sessionStorage = storageMock();
Run Code Online (Sandbox Code Playgroud)

  • 截至2016年,似乎这在现代浏览器中不起作用(已检查过Chrome和Firefox); 完全覆盖`localStorage`是不可能的. (7认同)
  • @a8m 将节点更新到 10.15.1 后,我收到错误消息,即`TypeError:无法设置只有 getter` 的 #<Window> 属性 localStorage,知道如何解决此问题吗? (3认同)
  • 是的,不幸的是这不再起作用,但我也认为`storage[key] || null` 不正确。如果`storage[key] === 0` 它将返回`null`。我认为您可以在存储中执行`返回键吗?storage[key] : null`。 (2认同)

Cla*_*ijo 19

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

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)
Run Code Online (Sandbox Code Playgroud)

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

因为替换真正的localStorage对象上的方法显然是不可靠的,所以使用"哑"mockStorage并根据需要存根各个方法,例如:

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');
Run Code Online (Sandbox Code Playgroud)


Cha*_*rdy 11

这就是我做的......

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,
});
Run Code Online (Sandbox Code Playgroud)


roo*_*oo2 8

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

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

代替spyOn(localStorage,'setItem')使用

spyOn(Storage.prototype, 'setItem')
spyOn(Storage.prototype, 'getItem')
Run Code Online (Sandbox Code Playgroud)

摘自bzbarskyteogeos在这里的回复https://github.com/jasmine/jasmine/issues/299

  • 你的评论应该会得到更多的喜欢。谢谢你! (3认同)

use*_*621 6

那里有没有图书馆要模拟localStorage

我刚写了一篇:

(function () {
    var localStorage = {};
    localStorage.setItem = function (key, val) {
         this[key] = val + '';
    }
    localStorage.getItem = function (key) {
        return this[key];
    }
    Object.defineProperty(localStorage, 'length', {
        get: function () { return Object.keys(this).length - 2; }
    });

    // Your tests here

})();
Run Code Online (Sandbox Code Playgroud)

我的初步测试显示localStorage拒绝在firefox中分配

仅在全球范围内.使用上面的包装函数,它工作得很好.


小智 5

根据某些答案中的建议覆盖localStorage全局window对象的属性在大多数 JS 引擎中不起作用,因为它们将localStoragedata 属性声明为不可写且不可配置。

但是我发现至少在 PhantomJS(版本 1.9.8)WebKit 版本中,您可以使用旧版 API__defineGetter__来控制localStorage访问时会发生什么。如果这也适用于其他浏览器,那将会很有趣。

var tmpStorage = window.localStorage;

// replace local storage
window.__defineGetter__('localStorage', function () {
    throw new Error("localStorage not available");
    // you could also return some other object here as a mock
});

// do your tests here    

// restore old getter to actual local storage
window.__defineGetter__('localStorage',
                        function () { return tmpStorage });
Run Code Online (Sandbox Code Playgroud)

这种方法的好处是您不必修改要测试的代码。


Tha*_*you 5

您不必将存储对象传递给使用它的每个方法。相反,您可以为接触存储适配器的任何模块使用配置参数。

你的旧模块

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

// hard to test !
export const anotherFunction () {
  return window.localStorage.getItem('foo')
}
Run Code Online (Sandbox Code Playgroud)

具有配置“包装器”功能的新模块

export default function (storage) {
  return {
    someFunction (x) {
      storage.setItem('foo', x)
    }
    anotherFunction () {
      storage.getItem('foo')
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

当您在测试代码中使用该模块时

// 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')
})
Run Code Online (Sandbox Code Playgroud)

这个MockStorage类可能看起来像这样

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()
  }
}
Run Code Online (Sandbox Code Playgroud)

在生产代码中使用你的模块时,改为传递真正的 localStorage 适配器

const myModule = require('./my-module')(window.localStorage)
Run Code Online (Sandbox Code Playgroud)