【发布时间】:2015-12-30 22:15:21
【问题描述】:
我在 Jest 测试中不断收到“localStorage 未定义”,这是有道理的,但我的选择是什么?撞砖墙。
【问题讨论】:
标签: jestjs
我在 Jest 测试中不断收到“localStorage 未定义”,这是有道理的,但我的选择是什么?撞砖墙。
【问题讨论】:
标签: jestjs
在此帮助下解决了这个问题:https://groups.google.com/forum/#!topic/jestjs/9EPhuNWVYTg
设置一个包含以下内容的文件:
var localStorageMock = (function() {
var store = {};
return {
getItem: function(key) {
return store[key];
},
setItem: function(key, value) {
store[key] = value.toString();
},
clear: function() {
store = {};
},
removeItem: function(key) {
delete store[key];
}
};
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
然后在 Jest 配置下的 package.json 中添加以下行
"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",
【讨论】:
"setupFiles": [...] 也可以。使用数组选项,允许将模拟分成单独的文件。例如:"setupFiles": ["<rootDir>/__mocks__/localStorageMock.js"]
getItem 的返回值与未针对特定键设置数据时浏览器返回的值略有不同。例如,在未设置时调用 getItem("foo") 将在浏览器中返回 null,但通过此模拟调用 undefined - 这导致我的一个测试失败。对我来说简单的解决方案是在 getItem 函数中返回 store[key] || null
localStorage['test'] = '123'; localStorage.getItem('test')之类的操作,这将不起作用
@chiedo 提供的出色解决方案
不过,我们使用的是 ES2015 语法,我觉得这样写更简洁一些。
class LocalStorageMock {
constructor() {
this.store = {};
}
clear() {
this.store = {};
}
getItem(key) {
return this.store[key] || null;
}
setItem(key, value) {
this.store[key] = String(value);
}
removeItem(key) {
delete this.store[key];
}
}
global.localStorage = new LocalStorageMock;
【讨论】:
value + ''来正确处理空值和未定义值
|| null,这就是我的测试失败的原因,因为在我的测试中我使用的是 not.toBeDefined()。 @Chiedo 解决方案让它再次工作
处理undefined 值(它没有toString())并在值不存在时返回null 的更好选择。用react v15、redux 和redux-auth-wrapper 测试了这个
class LocalStorageMock {
constructor() {
this.store = {}
}
clear() {
this.store = {}
}
getItem(key) {
return this.store[key] || null
}
setItem(key, value) {
this.store[key] = value
}
removeItem(key) {
delete this.store[key]
}
}
global.localStorage = new LocalStorageMock
【讨论】:
removeItem: developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem
【讨论】:
在这里为使用 Typescript 的项目解决了一些其他答案。我像这样创建了一个 LocalStorageMock:
export class LocalStorageMock {
private store = {}
clear() {
this.store = {}
}
getItem(key: string) {
return this.store[key] || null
}
setItem(key: string, value: string) {
this.store[key] = value
}
removeItem(key: string) {
delete this.store[key]
}
}
然后我创建了一个 LocalStorageWrapper 类,用于对应用程序中的本地存储进行所有访问,而不是直接访问全局本地存储变量。可以轻松地在包装器中设置模拟以进行测试。
【讨论】:
如果使用 create-react-app,documentation 中解释了一个更简单直接的解决方案。
创建src/setupTests.js 并将其放入其中:
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn()
};
global.localStorage = localStorageMock;
Tom Mertz 在下方评论中的贡献:
然后您可以通过执行类似的操作来测试是否使用了 localStorageMock 的函数
expect(localStorage.getItem).toBeCalledWith('token')
// or
expect(localStorage.getItem.mock.calls.length).toBe(1)
如果你想确保它被调用,在你的测试里面。查看https://facebook.github.io/jest/docs/en/mock-functions.html
【讨论】:
localStorage。 (如果你使用create-react-app 以及它自然提供的所有自动脚本)
expect(localStorage.getItem).toBeCalledWith('token')或expect(localStorage.getItem.mock.calls.length).toBe(1)的操作来测试你的localStorageMock的函数是否被使用。查看facebook.github.io/jest/docs/en/mock-functions.html
localStorage 的测试,这不会导致问题吗?您不想在每次测试后重置间谍以防止“溢出”到其他测试吗?
正如@ck4 建议的那样,documentation 对在玩笑中使用localStorage 有明确的解释。然而,模拟函数未能执行任何localStorage 方法。
下面是我的 react 组件的详细示例,它使用抽象方法来写入和读取数据,
//file: storage.js
const key = 'ABC';
export function readFromStore (){
return JSON.parse(localStorage.getItem(key));
}
export function saveToStore (value) {
localStorage.setItem(key, JSON.stringify(value));
}
export default { readFromStore, saveToStore };
错误:
TypeError: _setupLocalStorage2.default.setItem is not a function
修复:
为玩笑添加下面的模拟函数(路径:.jest/mocks/setUpStore.js)
let mockStorage = {};
module.exports = window.localStorage = {
setItem: (key, val) => Object.assign(mockStorage, {[key]: val}),
getItem: (key) => mockStorage[key],
clear: () => mockStorage = {}
};
片段引用自here
【讨论】:
目前(2019 年 10 月)localStorage 不能像往常一样被 jest 模拟或监视,正如 create-react-app 文档中所述。这是由于在 jsdom 中所做的更改。您可以在 jest 和 jsdom 问题跟踪器中了解它。
作为一种解决方法,您可以改为监视原型:
// does not work:
jest.spyOn(localStorage, "setItem");
localStorage.setItem = jest.fn();
// works:
jest.spyOn(window.localStorage.__proto__, 'setItem');
window.localStorage.__proto__.setItem = jest.fn();
// assertions as usual:
expect(localStorage.setItem).toHaveBeenCalled();
【讨论】:
jest.spyOn(window.localStorage.__proto__, 'setItem');
如果您正在寻找模拟而不是存根,这是我使用的解决方案:
export const localStorageMock = {
getItem: jest.fn().mockImplementation(key => localStorageItems[key]),
setItem: jest.fn().mockImplementation((key, value) => {
localStorageItems[key] = value;
}),
clear: jest.fn().mockImplementation(() => {
localStorageItems = {};
}),
removeItem: jest.fn().mockImplementation((key) => {
localStorageItems[key] = undefined;
}),
};
export let localStorageItems = {}; // eslint-disable-line import/no-mutable-exports
我导出存储项目以便于初始化。 IE。我可以很容易地将它设置为一个对象
在较新版本的 Jest + JSDom 中,无法设置此项,但本地存储已经可用,您可以像这样监视它:
const setItemSpy = jest.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem');
【讨论】:
以下解决方案兼容使用更严格的 TypeScript、ESLint、TSLint 和 Prettier 配置进行测试:{ "proseWrap": "always", "semi": false, "singleQuote": true, "trailingComma": "es5" }:
class LocalStorageMock {
public store: {
[key: string]: string
}
constructor() {
this.store = {}
}
public clear() {
this.store = {}
}
public getItem(key: string) {
return this.store[key] || undefined
}
public setItem(key: string, value: string) {
this.store[key] = value.toString()
}
public removeItem(key: string) {
delete this.store[key]
}
}
/* tslint:disable-next-line:no-any */
;(global as any).localStorage = new LocalStorageMock()
HT/ https://stackoverflow.com/a/51583401/101290 了解如何更新 global.localStorage
【讨论】:
describe('getToken', () => {
const Auth = new AuthService();
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ik1yIEpvc2VwaCIsImlkIjoiNWQwYjk1Mzg2NTVhOTQ0ZjA0NjE5ZTA5IiwiZW1haWwiOiJ0cmV2X2pvc0Bob3RtYWlsLmNvbSIsInByb2ZpbGVVc2VybmFtZSI6Ii9tcmpvc2VwaCIsInByb2ZpbGVJbWFnZSI6Ii9Eb3Nlbi10LUdpci1sb29rLWN1dGUtbnVrZWNhdDMxNnMtMzExNzAwNDYtMTI4MC04MDAuanBnIiwiaWF0IjoxNTYyMzE4NDA0LCJleHAiOjE1OTM4NzYwMDR9.YwU15SqHMh1nO51eSa0YsOK-YLlaCx6ijceOKhZfQZc';
beforeEach(() => {
global.localStorage = jest.fn().mockImplementation(() => {
return {
getItem: jest.fn().mockReturnValue(token)
}
});
});
it('should get the token from localStorage', () => {
const result = Auth.getToken();
expect(result).toEqual(token);
});
});
创建一个模拟并将其添加到global 对象
【讨论】:
我从github找到了这个解决方案
var localStorageMock = (function() {
var store = {};
return {
getItem: function(key) {
return store[key] || null;
},
setItem: function(key, value) {
store[key] = value.toString();
},
clear: function() {
store = {};
}
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});
您可以在 setupTests 中插入此代码,它应该可以正常工作。
我在一个项目中使用 typesctipt 对其进行了测试。
【讨论】:
要在 Typescript 中执行相同操作,请执行以下操作:
设置一个包含以下内容的文件:
let localStorageMock = (function() {
let store = new Map()
return {
getItem(key: string):string {
return store.get(key);
},
setItem: function(key: string, value: string) {
store.set(key, value);
},
clear: function() {
store = new Map();
},
removeItem: function(key: string) {
store.delete(key)
}
};
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
然后在 Jest 配置下的 package.json 中添加以下行
"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",
或者你在你想要模拟本地存储的测试用例中导入这个文件。
【讨论】:
您可以使用这种方法来避免嘲笑。
Storage.prototype.getItem = jest.fn(() => expectedPayload);
【讨论】:
你需要用这个 sn-ps 模拟本地存储
// localStorage.js
var localStorageMock = (function() {
var store = {};
return {
getItem: function(key) {
return store[key] || null;
},
setItem: function(key, value) {
store[key] = value.toString();
},
clear: function() {
store = {};
}
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});
在开玩笑的配置中:
"setupFiles":["localStorage.js"]
随便问什么。
【讨论】:
很遗憾,我在这里找到的解决方案对我不起作用。
所以我在查看 Jest GitHub 问题时发现了这个 thread
最受好评的解决方案是:
const spy = jest.spyOn(Storage.prototype, 'setItem');
// or
Storage.prototype.getItem = jest.fn(() => 'bla');
【讨论】:
window 或 Storage。也许这是我使用的旧版本的 Jest。
这对我有用,
delete global.localStorage;
global.localStorage = {
getItem: () =>
}
【讨论】:
我想我会在 TypeScript w/ React 中添加另一个非常适合我的解决方案:
我创建了一个mockLocalStorage.ts
export const mockLocalStorage = () => {
const setItemMock = jest.fn();
const getItemMock = jest.fn();
beforeEach(() => {
Storage.prototype.setItem = setItemMock;
Storage.prototype.getItem = getItemMock;
});
afterEach(() => {
setItemMock.mockRestore();
getItemMock.mockRestore();
});
return { setItemMock, getItemMock };
};
我的组件:
export const Component = () => {
const foo = localStorage.getItem('foo')
return <h1>{foo}</h1>
}
然后在我的测试中我像这样使用它:
import React from 'react';
import { mockLocalStorage } from '../../test-utils';
import { Component } from './Component';
const { getItemMock, setItemMock } = mockLocalStorage();
it('fetches something from localStorage', () => {
getItemMock.mockReturnValue('bar');
render(<Component />);
expect(getItemMock).toHaveBeenCalled();
expect(getByText(/bar/i)).toBeInTheDocument()
});
it('expects something to be set in localStorage' () => {
const value = "value"
const key = "key"
render(<Component />);
expect(setItemMock).toHaveBeenCalledWith(key, value);
}
【讨论】:
setItemMock?我有点困惑,而且对开玩笑的测试/测试也很陌生。
使用 TypeScript 和 Jest 的更优雅的解决方案。
interface Spies {
[key: string]: jest.SpyInstance
}
describe('→ Local storage', () => {
const spies: Spies = {}
beforeEach(() => {
['setItem', 'getItem', 'clear'].forEach((fn: string) => {
const mock = jest.fn(localStorage[fn])
spies[fn] = jest.spyOn(Storage.prototype, fn).mockImplementation(mock)
})
})
afterEach(() => {
Object.keys(spies).forEach((key: string) => spies[key].mockRestore())
})
test('→ setItem ...', async () => {
localStorage.setItem( 'foo', 'bar' )
expect(localStorage.getItem('foo')).toEqual('bar')
expect(spies.setItem).toHaveBeenCalledTimes(1)
})
})
【讨论】:
2021,打字稿
class LocalStorageMock {
store: { [k: string]: string };
length: number;
constructor() {
this.store = {};
this.length = 0;
}
/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/Storage/key
* @returns
*/
key = (idx: number): string => {
const values = Object.values(this.store);
return values[idx];
};
clear() {
this.store = {};
}
getItem(key: string) {
return this.store[key] || null;
}
setItem(key: string, value: string) {
this.store[key] = String(value);
}
removeItem(key: string) {
delete this.store[key];
}
}
export default LocalStorageMock;
然后你可以使用它
global.localStorage = new LocalStorageMock();
【讨论】:
以上答案都不适合我。因此,经过一番挖掘,这就是我要工作的地方。归功于一些来源和其他答案。
我的全部要点:https://gist.github.com/ar-to/01fa07f2c03e7c1b2cfe6b8c612d4c6b
/**
* Build Local Storage object
* @see https://www.codeblocq.com/2021/01/Jest-Mock-Local-Storage/ for source
* @see https://stackoverflow.com/a/32911774/9270352 for source
* @returns
*/
export const fakeLocalStorage = () => {
let store: { [key: string]: string } = {}
return {
getItem: function (key: string) {
return store[key] || null
},
setItem: function (key: string, value: string) {
store[key] = value.toString()
},
removeItem: function (key: string) {
delete store[key]
},
clear: function () {
store = {}
},
}
}
/**
* Mock window properties for testing
* @see https://gist.github.com/mayank23/7b994385eb030f1efb7075c4f1f6ac4c for source
* @see https://github.com/facebook/jest/issues/6798#issuecomment-514266034 for sample implementation
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window#properties for window properties
* @param { string } property window property string but set to any due to some warnings
* @param { Object } value for property
*
* @example
*
* const testLS = {
* id: 5,
* name: 'My Test',
* }
* mockWindowProperty('localStorage', fakeLocalStorage())
* window.localStorage.setItem('currentPage', JSON.stringify(testLS))
*
*/
const mockWindowProperty = (property: string | any, value: any) => {
const { [property]: originalProperty } = window
delete window[property]
beforeAll(() => {
Object.defineProperty(window, property, {
configurable: true,
writable: true,
value,
})
})
afterAll(() => {
window[property] = originalProperty
})
}
export default mockWindowProperty
【讨论】:
正如a comment Niket Pathak 所述,
从 jest@24 / jsdom@11.11.0 及以上开始,localStorage 会自动模拟。
【讨论】:
Object.defineProperty(window, "localStorage", {
value: {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
},
});
或
jest.spyOn(Object.getPrototypeOf(localStorage), "getItem");
jest.spyOn(Object.getPrototypeOf(localStorage), "setItem");
【讨论】: