是否有任何库可以模拟localStorage
?
我一直在使用Sinon.JS进行大多数其他javascript模拟,并发现它非常棒。
我的初步测试显示localStorage拒绝在firefox(sadface)中分配,所以我可能需要某种黑客攻击:/
我现在的选择(如我所见)如下:
??????
您如何看待这些方法,您认为还有其他更好的方法吗?无论哪种方式,我都会将最终制作的“库”放在github上,以获得开源优势。
答案 0 :(得分:112)
这是使用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 = {};
});
});
如果要在所有测试中模拟本地存储,请在测试的全局范围内声明上面显示的beforeEach()
函数(通常的位置是 specHelper.js 脚本)。
答案 1 :(得分:44)
根据您的需要模拟全局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;
}
};
}
然后你真正做的是这样的事情:
// mock the localStorage
window.localStorage = storageMock();
// mock the sessionStorage
window.sessionStorage = storageMock();
答案 2 :(得分: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)
与模拟和单元测试一致,我喜欢避免测试存储实现。例如,在设置项目等之后检查存储长度是否增加没有意义。
由于替换真正的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');
答案 3 :(得分: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,
});
答案 4 :(得分: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
})();
我的初步测试显示localStorage拒绝在firefox中分配
仅在全球范围内。使用上面的包装函数,它可以正常工作。
答案 5 :(得分:4)
这是一个使用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();
答案 6 :(得分:4)
如某些答案中所建议的那样覆盖全局localStorage
对象的window
属性在大多数JS引擎中都不起作用,因为它们将localStorage
数据属性声明为不可写且不可配置。
但是我发现,至少使用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 });
这种方法的好处是您不必修改您即将测试的代码。
答案 7 :(得分:3)
您不必将存储对象传递给使用它的每个方法。相反,您可以为任何触及存储适配器的模块使用配置参数。
您的旧模块
// hard to test !
export const someFunction (x) {
window.localStorage.setItem('foo', x)
}
// hard to test !
export const anotherFunction () {
return window.localStorage.getItem('foo')
}
你的新模块带有config" wrapper"功能
export default function (storage) {
return {
someFunction (x) {
storage.setItem('foo', x)
}
anotherFunction () {
storage.getItem('foo')
}
}
}
在测试代码时使用模块
// 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')
})
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()
}
}
在生产代码中使用模块时,请传递真正的localStorage适配器
const myModule = require('./my-module')(window.localStorage)
答案 8 :(得分:3)
当前解决方案不适用于Firefox。这是因为html规范将localStorage定义为不可修改。但是,您可以通过直接访问localStorage的原型来解决此问题。
跨浏览器解决方案是在Storage.prototype
上模拟对象,例如
而不是 spyOn(localStorage,'setItem')使用
spyOn(Storage.prototype, 'setItem')
spyOn(Storage.prototype, 'getItem')
摘自 bzbarsky 和 teogeos 在这里https://github.com/jasmine/jasmine/issues/299
答案 9 :(得分:2)
学分 https://medium.com/@armno/til-mocking-localstorage-and-sessionstorage-in-angular-unit-tests-a765abdc9d87 进行伪造的本地存储,并在生成时监视本地存储
beforeAll( () => {
let store = {};
const mockLocalStorage = {
getItem: (key: string): string => {
return key in store ? store[key] : null;
},
setItem: (key: string, value: string) => {
store[key] = `${value}`;
},
removeItem: (key: string) => {
delete store[key];
},
clear: () => {
store = {};
}
};
spyOn(localStorage, 'getItem')
.and.callFake(mockLocalStorage.getItem);
spyOn(localStorage, 'setItem')
.and.callFake(mockLocalStorage.setItem);
spyOn(localStorage, 'removeItem')
.and.callFake(mockLocalStorage.removeItem);
spyOn(localStorage, 'clear')
.and.callFake(mockLocalStorage.clear);
})
在这里我们使用它
it('providing search value should return matched item', () => {
localStorage.setItem('defaultLanguage', 'en-US');
expect(...
});
答案 10 :(得分:1)
我决定重申我对Pumbaa80答案的评论作为单独答案,以便更容易将其重新用作图书馆。
我使用了Pumbaa80的代码,稍微改进了一下,添加了测试并将其作为npm模块发布在这里: https://www.npmjs.com/package/mock-local-storage
这是一个源代码: https://github.com/letsrock-today/mock-local-storage/blob/master/src/mock-localstorage.js
一些测试: https://github.com/letsrock-today/mock-local-storage/blob/master/test/mock-localstorage.js
模块在全局对象(窗口或全局,其中定义了哪些)上创建模拟localStorage和sessionStorage。
在我的其他项目测试中,我需要使用mocha:mocha -r mock-local-storage
使所有正在测试的代码都可以使用全局定义。
基本上,代码如下所示:
(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));
请注意,所有方法都是通过Object.defineProperty
添加的,以便它们不会作为常规项目进行迭代,访问或删除,并且不会计算长度。我还添加了一种注册回调的方法,当一个项目即将被放入对象时调用该回调。此回调可用于模拟测试中超出配额的错误。
答案 11 :(得分:0)
不幸的是,我们在测试场景中模拟localStorage对象的唯一方法是更改我们正在测试的代码。您必须将代码包装在匿名函数中(无论如何都应该这样做)并使用“依赖注入”来传递对窗口对象的引用。类似的东西:
(function (window) {
// Your code
}(window.mockWindow || window));
然后,在测试中,您可以指定:
window.mockWindow = { localStorage: { ... } };
答案 12 :(得分:0)
这就是我喜欢的方式。保持简单。
git remote add gitlab <gitlab_repo_url>
git push --all gitlab
答案 13 :(得分:0)
我发现不需要模拟它。我可以通过setItem
将实际的本地存储更改为所需的状态,然后仅查询值以查看是否通过getItem
进行了更改。它不像模拟那样强大,因为您看不到更改了多少次,但是它对我有用。
答案 14 :(得分:0)
需要与存储的数据进行交互
一种很短的方法
const store = {};
Object.defineProperty(window, 'localStorage', {
value: {
getItem:(key) => store[key]},
setItem:(key, value) => {
store[key] = value.toString();
},
clear: () => {
store = {};
}
},
});
用茉莉花间谍
如果您只需要这些功能即可使用茉莉花对其进行监视,它会更短并且更易于阅读。
Object.defineProperty(window, 'localStorage', {
value: {
getItem:(key) => {},
setItem:(key, value) => {},
clear: () => {},
...
},
});
const spy = spyOn(localStorage, 'getItem')
现在您根本不需要商店。
答案 15 :(得分:-1)
我知道 OP 专门询问了模拟,但可以说最好是 spy
而不是 mock
。如果您使用 Object.keys(localStorage)
迭代所有可用的键会怎样?你可以这样测试:
const someFunction = () => {
const localStorageKeys = Object.keys(localStorage)
console.log('localStorageKeys', localStorageKeys)
localStorage.removeItem('whatever')
}
测试代码如下:
describe('someFunction', () => {
it('should remove some item from the local storage', () => {
const _localStorage = {
foo: 'bar', fizz: 'buzz'
}
Object.setPrototypeOf(_localStorage, {
removeItem: jest.fn()
})
jest.spyOn(global, 'localStorage', 'get').mockReturnValue(_localStorage)
someFunction()
expect(global.localStorage.removeItem).toHaveBeenCalledTimes(1)
expect(global.localStorage.removeItem).toHaveBeenCalledWith('whatever')
})
})
不需要模拟或构造函数。行也相对较少。