如何在 JavaScript 单元测试中模拟 localStorage?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11485420/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to mock localStorage in JavaScript unit tests?
提问by Anthony Sottile
Are there any libraries out there to mock localStorage
?
有没有可以模拟的库localStorage
?
I've been using Sinon.JSfor most of my other javascript mocking and have found it is really great.
我一直在使用Sinon.JS进行大多数其他 javascript模拟,并且发现它真的很棒。
My initial testing shows that localStorage refuses to be assignable in firefox (sadface) so I'll probably need some sort of hack around this :/
我的初步测试表明 localStorage 拒绝在 Firefox (sadface) 中分配,所以我可能需要解决这个问题:/
My options as of now (as I see) are as follows:
我现在的选择(如我所见)如下:
- Create wrapping functions that all my code uses and mock those
- Create some sort of (might be complicated) state management (snapshot localStorage before test, in cleanup restore snapshot) for localStorage.
??????
- 创建我所有代码使用的包装函数并模拟它们
- 为 localStorage 创建某种(可能很复杂)状态管理(测试前的快照 localStorage,在清理恢复快照中)。
??????
What do you think of these approaches and do you think there are any other better ways to go about this? Either way I'll put the resulting "library" that I end up making on github for open source goodness.
您如何看待这些方法,您认为还有其他更好的方法来解决这个问题吗?无论哪种方式,我都会把我最终在 github 上制作的结果“库”放在开源的优点上。
回答by Andreas K?berle
Here is a simple way to mock it with Jasmine:
这是用 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.jsscript).
如果要在所有测试中模拟本地存储,请在测试beforeEach()
的全局范围内声明上面显示的函数(通常的位置是specHelper.js脚本)。
回答by a8m
just mock the global localStorage / sessionStorage (they have the same API) for your needs.
For example:
只需根据您的需要模拟全局 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;
}
};
}
And then what you actually do, is something like that:
然后你实际做的事情是这样的:
// mock the localStorage
window.localStorage = storageMock();
// mock the sessionStorage
window.sessionStorage = storageMock();
回答by Claudijo
Also consider the option to inject dependencies in an object's constructor function.
还要考虑在对象的构造函数中注入依赖项的选项。
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)
In line with mocking and unit testing, I like to avoid testing the storage implementation. For instance no point in checking if length of storage increased after you set an item, etc.
与模拟和单元测试一致,我喜欢避免测试存储实现。例如,在设置项目等后检查存储长度是否增加没有意义。
Since it is obviously unreliable to replace methods on the real localStorage object, use a "dumb" mockStorage and stub the individual methods as desired, such as:
由于替换真实 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');
回答by ChuckJHardy
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,
});
回答by roo2
The current solutions will not work in Firefox. This is because localStorage is defined by the html spec as being not modifiable. You can however get around this by accessing localStorage's prototype directly.
当前的解决方案在 Firefox 中不起作用。这是因为 localStorage 由 html 规范定义为不可修改。但是,您可以通过直接访问 localStorage 的原型来解决此问题。
The cross browser solution is to mock the objects on Storage.prototype
e.g.
跨浏览器的解决方案是模拟对象,Storage.prototype
例如
instead of spyOn(localStorage, 'setItem')use
而不是spyOn(localStorage, 'setItem')使用
spyOn(Storage.prototype, 'setItem')
spyOn(Storage.prototype, 'getItem')
taken from bzbarskyand teogeos's replies here https://github.com/jasmine/jasmine/issues/299
取自bzbarsky和 teogeos的回复https://github.com/jasmine/jasmine/issues/299
回答by user123444555621
Are there any libraries out there to mock
localStorage
?
有没有可以模拟的库
localStorage
?
I just wrote one:
我只写了一篇:
(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
})();
My initial testing shows that localStorage refuses to be assignable in firefox
我的初步测试表明 localStorage 拒绝在 Firefox 中分配
Only in global context. With a wrapper function as above, it works just fine.
仅在全球范围内。使用上面的包装函数,它工作得很好。
回答by Conrad Calmez
Overwriting the localStorage
property of the global window
object as suggested in some of the answers won't work in most JS engines, because they declare the localStorage
data property as not writable and not configurable.
根据某些答案中的建议覆盖localStorage
全局window
对象的属性在大多数 JS 引擎中不起作用,因为它们将localStorage
data 属性声明为不可写且不可配置。
However I found out that at least with PhantomJS's (version 1.9.8) WebKit version you could use the legacy API __defineGetter__
to control what happens if localStorage
is accessed. Still it would be interesting if this works in other browsers as well.
但是我发现至少在 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 });
The benefit of this approach is that you would not have to modify the code you're about to test.
这种方法的好处是您不必修改要测试的代码。
回答by Manuel Bitto
Here is an exemple using sinon spy and mock:
这是一个使用 sinon spy 和 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();
回答by Thank you
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
这个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()
}
}
When using your module in production code, instead pass the real localStorage adapter
在生产代码中使用你的模块时,改为传递真正的 localStorage 适配器
const myModule = require('./my-module')(window.localStorage)
回答by nikolay_turpitko
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.
我决定重申我对 Pumbaa80 答案的评论作为单独的答案,以便更容易将其作为库重用。
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.
我采用了 Pumbaa80 的代码,对其进行了一些改进,添加了测试并将其作为 npm 模块发布在这里: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
这是一个源代码: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
一些测试: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).
模块在全局对象上创建模拟 localStorage 和 sessionStorage(窗口或全局,其中定义了它们)。
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.
在我的其他项目的测试中,我要求它与 mocha 一起使用:mocha -r mock-local-storage
为所有被测代码提供全局定义。
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.
请注意,所有通过添加的方法Object.defineProperty
都不会作为常规项目被迭代、访问或删除,并且不会计算长度。我还添加了一种注册回调的方法,该回调在项目即将放入对象时调用。此回调可用于模拟测试中的配额超出错误。