如何在TypeScript单元测试中使用Jest模拟localStorage
我之前在TypeScript项目里用Jest模拟localStorage时,也遇到过一模一样的“ReferenceError: localStorage is not defined”报错,折腾了好一阵才搞定。结合你的需求(只需要模拟getItem和removeItem),给你分享两个亲测有效的方法:
方法一:单个测试文件内模拟(适合局部测试)
首先要解决TypeScript的类型问题,因为Node环境下默认没有window/localStorage的类型定义,我们需要先扩展全局类型,不然会报类型错误。在测试文件顶部加上这段:
// 扩展全局类型,让TypeScript识别localStorage declare module NodeJS { interface Global { localStorage: Storage; } }
然后定义我们的mock对象,只实现你需要的getItem和removeItem,其他方法如果TypeScript要求的话,可以空实现:
const mockLocalStorage = { getItem: jest.fn(), removeItem: jest.fn(), // 下面这几个是Storage接口要求的必选属性/method,空实现即可 setItem: jest.fn(), clear: jest.fn(), key: jest.fn(), length: 0 };
接下来在测试开始前,把这个mock挂载到全局对象上,并且每个测试前清空mock的调用记录:
beforeEach(() => { jest.clearAllMocks(); // 每次测试前重置mock Object.defineProperty(global, 'localStorage', { value: mockLocalStorage, }); });
现在就可以正常写测试了,举个例子:
// 假设你要测试的函数是这个 function getStoredUser() { return localStorage.getItem('user'); } test('getStoredUser能正确获取localStorage的值', () => { // 预设getItem的返回值 mockLocalStorage.getItem.mockReturnValue('{"name": "Guerlain"}'); const result = getStoredUser(); // 断言getItem被正确调用 expect(mockLocalStorage.getItem).toHaveBeenCalledWith('user'); // 断言返回值正确 expect(result).toBe('{"name": "Guerlain"}'); });
方法二:全局配置(所有测试文件通用)
如果你的很多测试都需要用到localStorage的mock,每次写一遍太麻烦,可以在Jest的全局setup文件里配置一次,所有测试都能用。
- 首先在项目里创建一个setup文件,比如
src/setupTests.ts,内容和方法一类似:
declare module NodeJS { interface Global { localStorage: Storage; } } const mockLocalStorage = { getItem: jest.fn(), removeItem: jest.fn(), setItem: jest.fn(), clear: jest.fn(), key: jest.fn(), length: 0 }; global.localStorage = mockLocalStorage; // 全局钩子:每个测试前清空mock beforeEach(() => { jest.clearAllMocks(); });
- 然后在Jest的配置文件(比如
jest.config.ts)里指定这个setup文件:
export default { // 其他配置... setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'], };
这样所有测试文件里都能直接使用localStorage的mock了,不用再重复写类型扩展和挂载代码。
为什么你之前的方法没生效?
你之前尝试用jest.mock('localStorage', ...)是不对的,因为localStorage是浏览器环境的全局对象,不是一个可以用import导入的模块,所以Jest找不到这个模块,自然mock失败。正确的做法是直接把mock挂载到global对象上,模拟浏览器的全局环境。
备注:内容来源于stack exchange,提问作者Guerlain BLANCHARD guerlblanch

