我在 Jest 测试中不断得到"本地存储未定义",这是有道理的,但我有什么选择?撞砖墙。
@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;
在以下的帮助下弄清楚了: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",
答案:
目前(22 年 7 月(本地存储不能像往常那样被开玩笑嘲笑或监视,正如创建反应应用程序文档中概述的那样。这是由于在 jsdom 中所做的更改。您可以在jest和jsdom问题跟踪器中阅读有关它的信息。
作为解决方法,您可以监视原型:
// does not work:
jest.spyOn(localStorage, "setItem");
localStorage.setItem = jest.fn();
// either of these lines will work, different syntax that does the same thing:
jest.spyOn(Storage.prototype, 'setItem');
Storage.prototype.setItem = jest.fn();
// assertions as usual:
expect(localStorage.setItem).toHaveBeenCalled();
关于监视原型的说明:
监视实例使您能够观察和模拟特定对象的行为。
另一方面,监视原型将一次观察/操作该类的每个实例。除非您有特殊的用例,否则这可能不是您想要的。
虽然在许多情况下处理localStorage
,使用原型是可以接受的,@onlywei指出了一个重要的警告:
此策略将不允许您区分
localStorage
和sessionStorage
,因为两者都是Storage
的实例,并从存储原型继承。
对于处理这两个概念的实现,您需要考虑具体情况和影响。
如果使用create-react-app,文档中有一个更简单,更直接的解决方案。
创建src/setupTests.js
并将其放入其中:
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn()
};
global.localStorage = localStorageMock;
汤姆·默茨在下面的评论中的贡献:
然后,您可以通过执行以下操作来测试您的 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
不幸的是,我在这里找到的解决方案对我不起作用。
所以我正在查看 Jest GitHub 问题并找到了这个线程
投票最多的解决方案是以下解决方案:
const spy = jest.spyOn(Storage.prototype, 'setItem');
// or
Storage.prototype.getItem = jest.fn(() => 'bla');
你只需要像这样做一个模拟包:
https://www.npmjs.com/package/jest-localstorage-mock
它不仅处理存储功能,还允许您测试是否实际调用了存储。
一个更好的选择,它处理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
如果您正在寻找模拟而不是存根,这是我使用的解决方案:
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
我导出存储项以便于初始化。 即我可以轻松地将其设置为对象
在较新版本的Jest + JSDom中,无法设置它,但是本地存储已经可用,您可以像这样监视它:
const setItemSpy = jest.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem');
对于Jest
、React
和TypeScript
用户:
我创建了一个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')
localStorage.setItem('bar', 'true')
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 = "true"
const key = "bar"
render(<Component />);
expect(setItemMock).toHaveBeenCalledWith(key, value);
}
我从 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
});
您可以将此代码插入到安装程序测试中,它应该可以正常工作。
我用typectipt在一个项目中测试了它。
使用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)
})
})
2022 年的更新。
Jest@24+ 能够自动模拟本地存储。但是,默认情况下,所需的依赖项不再随之而来。
npm i -D jest-environment-jsdom
您还需要更改 Jest 测试模式:
// jest.config.cjs
module.exports = {
...
testEnvironment: "jsdom",
...
};
现在本地存储已经为您模拟了。
例:
// myStore.js
const saveLocally = (key, value) => {
localStorage.setItem(key, value)
};
测试:
// myStore.spec.ts
import { saveLocally } from "./myStore.js"
it("saves key-value pair", () => {
let key = "myKey";
let value = "myValue";
expect(localStorage.getItem(key)).toBe(null);
saveLocally(key, value);
expect(localStorage.getItem(key)).toBe(value);
};
您可以使用此方法以避免嘲笑。
Storage.prototype.getItem = jest.fn(() => expectedPayload);
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");
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();
正如@ck4建议的那样,文档对在开玩笑中使用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 = {}
};
代码段从这里引用
要在打字稿中执行相同的操作,请执行以下操作:
设置包含以下内容的文件:
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",
或者,您将此文件导入要模拟本地存储的测试用例中。
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
对象
至少到目前为止,localStorage可以很容易地在你的玩笑测试中被监视,例如:
const spyRemoveItem = jest.spyOn(window.localStorage, 'removeItem')
仅此而已。您可以像以前一样使用间谍。
正如Niket Pathak的评论中提到的,从jest@24/jsdom@11.12.0 及更高版本开始,localStorage
会自动模拟。
这对我有用,只有一个代码行
const setItem = jest.spyOn(Object.getPrototypeOf(localStorage), 'setItem');
提出了一些其他答案,以解决带有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 类,用于对应用程序中的本地存储的所有访问,而不是直接访问全局本地存储变量。可以轻松地在包装器中设置模拟进行测试。
以下解决方案兼容使用更严格的 TypeScript、ESLint、TSLint 和更漂亮的配置进行测试: { "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
模拟 localStorage - 只需使用 jsdom 环境,以便您的测试在类似浏览器的条件下运行。
在你的jest.config.js,
module.exports = {
// ...
testEnvironment: "jsdom"
}
上面的答案都不适合我。所以经过一番挖掘,这就是我开始的工作。功劳也归功于一些来源和其他答案。
- https://www.codeblocq.com/2021/01/Jest-Mock-Local-Storage/
- https://github.com/facebook/jest/issues/6798#issuecomment-440988627
- https://gist.github.com/mayank23/7b994385eb030f1efb7075c4f1f6ac4c
- https://github.com/facebook/jest/issues/6798#issuecomment-514266034
我的全部要点: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
就我而言,我需要在检查之前设置 localStorage 值。
所以我所做的是
const data = { .......}
const setLocalStorageValue = (name: string, value: any) => {
localStorage.setItem(name, JSON.stringify(value))
}
describe('Check X class', () => {
setLocalStorageValue('Xname', data)
const xClass= new XClass()
console.log(xClass.initiate()) ; // it will work
})
2022 年 12 月:Nx 14 与 Angular 14 开玩笑。我们在每个应用程序和库文件夹中都有一个test-setup.ts
文件。我们设置本地存储模拟全局。
import 'jest-preset-angular/setup-jest';
Storage.prototype.getItem = jest.fn();
Storage.prototype.setItem = jest.fn();
Storage.prototype.removeItem = jest.fn();
然后localStorage.service.spec.ts
文件如下所示:
import { LocalStorageService } from './localstorage.service';
describe('LocalStorageService', () => {
let localStorageService: LocalStorageService;
beforeEach(() => {
localStorageService = new LocalStorageService();
});
it('should set "identityKey" in localStorage', async () => {
localStorageService.saveData('identityKey', '99');
expect(window.localStorage.setItem).toHaveBeenCalled();
expect(window.localStorage.setItem).toHaveBeenCalledWith('identityKey', '99');
expect(window.localStorage.setItem).toHaveBeenCalledTimes(1);
});
it('should get "identityKey" from localStorage', async () => {
localStorageService.getData('identityKey');
expect(window.localStorage.getItem).toHaveBeenCalled();
expect(window.localStorage.getItem).toHaveBeenCalledWith('identityKey');
expect(window.localStorage.getItem).toHaveBeenCalledTimes(1);
});
it('should remove "identityKey" from localStorage', async () => {
localStorageService.removeData('identityKey');
expect(window.localStorage.removeItem).toHaveBeenCalled();
expect(window.localStorage.removeItem).toHaveBeenCalledWith('identityKey');
expect(window.localStorage.removeItem).toHaveBeenCalledTimes(1);
});
});
附言对不起,缩进不好,这个卫星溢出窗口s*cks。
首先:我创建了一个名为localStorage.ts(localStorage.js(的文件
class LocalStorageMock {
store: Store;
length: number;
constructor() {
this.store = {};
this.length = 0;
}
key(n: number): any {
if (typeof n === 'undefined') {
throw new Error(
"Uncaught TypeError: Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present."
);
}
if (n >= Object.keys(this.store).length) {
return null;
}
return Object.keys(this.store)[n];
}
getItem(key: string): Store | null {
if (!Object.keys(this.store).includes(key)) {
return null;
}
return this.store[key];
}
setItem(key: string, value: any): undefined {
if (typeof key === 'undefined' && typeof value === 'undefined') {
throw new Error(
"Uncaught TypeError: Failed to execute 'setItem' on 'Storage': 2 arguments required, but only 0 present."
);
}
if (typeof value === 'undefined') {
throw new Error(
"Uncaught TypeError: Failed to execute 'setItem' on 'Storage': 2 arguments required, but only 1 present."
);
}
if (!key) return undefined;
this.store[key] = value.toString() || '';
this.length = Object.keys(this.store).length;
return undefined;
}
removeItem(key: string): undefined {
if (typeof key === 'undefined') {
throw new Error(
"Uncaught TypeError: Failed to execute 'removeItem' on 'Storage': 1 argument required, but only 0 present."
);
}
delete this.store[key];
this.length = Object.keys(this.store).length;
return undefined;
}
clear(): undefined {
this.store = {};
this.length = 0;
return undefined;
}
}
export const getLocalStorageMock = (): any => {
return new LocalStorageMock();
};
global.localStorage = new LocalStorageMock();
然后创建一个名为 session.test.ts(session.test.js( 的测试文件
import { getLocalStorageMock } from '../localstorage';
describe('session storage', () => {
let localStorage;
beforeEach(() => {
localStorage = getLocalStorageMock();
});
describe('getItem', () => {
it('should return null if the item is undefined', () => {
expect(localStorage.getItem('item')).toBeNull();
});
it("should return '' instead of null", () => {
localStorage.setItem('item', '');
expect(localStorage.getItem('item')).toBe('');
});
it('should return navid', () => {
localStorage.setItem('item', 'navid');
expect(localStorage.getItem('item')).toBe('navid');
});
});
});
从 2023 年和 jest 29 开始,您只需通过以下方式添加 jest-environment-jsdom 包
npm install --save-dev jest-environment-jsdom
并通过以下方式激活它package.json
"jest": { "testEnvironment": "jsdom"}
现在您可以使用window.localStorage.setItem()
和window.localStorage.getItem()
等。
我有用,
delete global.localStorage;
global.localStorage = {
getItem: () =>
}