如何使用Jest为单测模拟自定义全局window变量?
问题描述
我正尝试为单个测试用例模拟自定义全局window变量。我有一个React组件MyComponent.tsx,它引用了服务器端index.html中定义的全局window变量:
<!-- index.html --> <script> var globalVars = { myVar: "value" }; </script>
// MyComponent.tsx function MyComponent() { const { myWindowVar } = constants; return <h1>{myWindowVar}</h1>; }
// constants.ts const constants = { myWindowVar: window?.globalVars?.myVar };
我尝试了几种方法模拟该变量,但似乎都未在MyComponent渲染前生效,导致测试失败:
// MyComponent.test.js test("Test mocked window variable", () => { // 1. 直接模拟global/window对象无效 // global.globalVars = { myVar: "mocked value" }; // 2. 此方法也无效 // Object.defineProperty(window, "globalVars", { // value: { myVar: "mocked value" }, // writable: true // }); // 3. 监听window对象并更新变量同样无效 const originalWindow = { ...window }; const windowSpy = jest.spyOn(global, "window", "get"); windowSpy.mockImplementation(() => ({ ...originalWindow, globalVars: { myVar: "mocked value" } })); render(<MyComponent />); expect(screen.getByText("mocked value")).toBeInTheDocument(); windowSpy.mockRestore(); });
请问是否可以实现?如何在组件渲染前,使用Jest为单个测试用例模拟这个自定义全局window变量?
解决方案
问题核心在于constants.ts中的constants对象是模块加载阶段就已计算完成的,而非组件渲染时动态读取。你之前的模拟操作虽然在组件渲染前执行,但constants在测试文件导入MyComponent的瞬间就已经初始化,导致模拟值无法被读取到。
以下是两种可行的解决思路:
思路1:将常量改为动态获取的函数
修改constants.ts,把静态对象改为返回值的函数,让组件每次渲染时都重新读取window变量:
// constants.ts export const getConstants = () => ({ myWindowVar: window?.globalVars?.myVar });
组件中调用该函数:
// MyComponent.tsx import { getConstants } from './constants'; function MyComponent() { const { myWindowVar } = getConstants(); return <h1>{myWindowVar}</h1>; }
此时再使用Object.defineProperty模拟window变量即可生效,因为每次渲染都会重新读取最新的window值。
思路2:测试中动态导入模块
如果不想修改业务代码,可以利用Jest的模块重置能力,确保模拟变量后再加载constants和组件:
// MyComponent.test.js test("Test mocked window variable", async () => { // 重置所有已加载的模块 jest.resetModules(); // 模拟window上的全局变量 Object.defineProperty(window, "globalVars", { value: { myVar: "mocked value" }, writable: true }); // 动态导入组件和测试库(此时constants会重新计算并读取模拟值) const { default: MyComponent } = await import('./MyComponent'); const { screen, render } = await import('@testing-library/react'); render(<MyComponent />); expect(screen.getByText("mocked value")).toBeInTheDocument(); // 清理模拟 delete window.globalVars; });
这种方法的关键是让constants模块在模拟window之后才被加载,确保初始化时能读取到模拟值。
补充说明
- 测试中直接操作
window对象即可,无需修改global,因为浏览器环境中全局变量挂载在window上,Jest测试环境也遵循这一逻辑。 - 若使用TypeScript,需添加全局类型声明避免报错:
// global.d.ts declare global { interface Window { globalVars?: { myVar?: string; }; } } export {};
内容的提问来源于stack exchange,提问作者user2753924
相关产品推荐
相关产品推荐

