Jest覆盖率测试:全局对象与反向条件测试困惑求助
解决Jest中window.foo为undefined场景的测试与覆盖率问题
这个问题我之前也遇到过,Jest的覆盖率提示确实容易让人困惑,特别是这种判断全局对象是否存在的分支。我来给你一步步讲怎么实现你想要的测试:
核心思路
我们需要做到两点:
- 跟踪
window.foo是否被访问过(且仅访问一次) - 确保
window.foo.bar从未被调用(因为window.foo为undefined时,代码不会进入if块)
因为直接给window.foo赋值undefined没法监听访问行为,所以我们需要用getter拦截或者Jest的spyOn来跟踪属性访问。
具体实现步骤
1. 测试前的准备与清理
首先要确保测试之间不会互相污染,所以在测试前后恢复window的原始状态:
afterEach(() => { // 删除我们添加的foo属性 delete window.foo; // 恢复所有Jest mock jest.restoreAllMocks(); });
2. 编写测试用例
这里提供两种可行的方案:
方案一:用自定义getter跟踪访问次数
这种方式直观,手动计数window.foo的访问次数:
test('不调用window.foo.bar当window.foo为undefined时', () => { let fooAccessCount = 0; // 给window添加foo属性,用getter拦截访问并计数 Object.defineProperty(window, 'foo', { get: () => { fooAccessCount++; return undefined; }, configurable: true // 允许测试后删除该属性 }); // 执行你的代码逻辑(替换成包含if(window.foo)的函数或代码) yourFunctionWithFooCheck(); // 断言foo被访问了一次(对应if判断的那一次读取) expect(fooAccessCount).toBe(1); // 额外验证:代码不会尝试访问foo.bar(因为foo是undefined,访问bar会抛错,所以断言代码执行时无错误) expect(() => yourFunctionWithFooCheck()).not.toThrow(TypeError); });
方案二:用Jest的spyOn监听getter
Jest的spyOn支持监听对象属性的getter,更符合Jest的惯用写法:
test('不调用window.foo.bar当window.foo为undefined时', () => { // 监听window.foo的getter,mock返回undefined const fooGetterSpy = jest.spyOn(window, 'foo', 'get').mockReturnValue(undefined); // 执行你的代码逻辑 yourFunctionWithFooCheck(); // 断言foo的getter被调用了一次 expect(fooGetterSpy).toHaveBeenCalledTimes(1); // 确认bar从未被调用:因为foo是undefined,代码不会进入if块,所以根本不会尝试访问bar // 如果想更严谨,可以提前mock一个bar(虽然这里不会用到),断言它从未被调用 const mockBar = jest.fn(); // 这里只是演示,实际测试foo为undefined时不需要这一步 // Object.defineProperty(window, 'foo', { value: { bar: mockBar }, configurable: true }); // expect(mockBar).not.toHaveBeenCalled(); });
关键说明
- 当
window.foo为undefined时,你的代码逻辑if (window.foo) { window.foo.bar(); }会直接跳过if块,所以根本不会尝试访问window.foo.bar,这也是为什么我们只需要断言window.foo被访问一次,同时代码没有抛出Cannot read property 'bar' of undefined的错误即可。 - 如果你是直接在全局作用域写的这段代码(不是在函数里),需要用Jest的模块重新加载机制(比如
jest.isolateModules)来确保每次测试都重新执行代码,否则代码只会在模块加载时执行一次。
内容的提问来源于stack exchange,提问作者Jamadan
相关产品推荐
相关产品推荐

