在导入依赖项之前,使用Jest模拟window对象

10

在导入依赖项之前,我需要在窗口对象中设置一个值。假设我有以下代码:

// foo.test.js
import { dependency } from './foo'

describe('...', () => {
  it('...', () => {
    // use dependency
  })
})

但是为了导入依赖项,我需要在 window.myValues 中定义一个值。

// foo.js
export const dependency = {
  key: window.myValue.nestedValue
}

当导入该文件时,该代码会给我一个错误,因为window.myValue.nestedValue试图访问未定义的nestedValue属性。

我该如何解决这个问题?

编辑根据下面christianeide的答案,我得到了以下错误:

   Test suite failed to run

    TypeError: Cannot convert undefined or null to object

      2 |   delete global.window.myValue
      3 |   global.window = Object.create(window)
    > 4 |   global.window.myValue = {
        |                 ^
      5 |     nestedValue: 'someValue'
      6 |   }
      7 | }

      at module.exports (jest.setup.js:4:17)
      at node_modules/@jest/core/build/runGlobalHook.js:82:17
      at ScriptTransformer.requireAndTranspileModule (node_modules/@jest/transform/build/ScriptTransformer.js:684:24)
      at node_modules/@jest/core/build/runGlobalHook.js:72:27
      at pEachSeries (node_modules/p-each-series/index.js:8:9)
      at async _default (node_modules/@jest/core/build/runGlobalHook.js:58:5)
      at async runJest (node_modules/@jest/core/build/runJest.js:345:5)

这个回答解决了你的问题吗?如何使用Jest模拟JavaScript的'window'对象? - Michael Freidgeim
4个回答

8

ES6的导入语句是“提升”的,意味着不论你在代码的哪个位置写它们,它们都会在导入模块被执行之前得到处理,因此被导入的模块总是在导入模块之前被执行。在你的情况下,这意味着foo.js在foo.test.js之前被执行,所以即使你在测试中正确地模拟了window属性,foo.js也不会看到你的模拟。

你可以通过在测试中使用require来导入经过window属性模拟后的foo.js来解决这个问题。

// foo.test.js
window.myValue = { nestedValue: MOCK_NESTED_VALUE };

const { dependency } = require('./foo');

describe('...', () => {
  it('...', () => {
    // use dependency
  })
})

正如其他答案所指出的,如果myValuewindow的现有“系统”属性之一,例如window.location,您可能需要先删除它。在删除时,请不要忘记备份,以便在测试清理后可以恢复它。


2
太棒了!这正是我在寻找的。谢谢! - Daniel Reina

1

我使用jest.mock解决了这样一个问题:由于它在导入语句之前被提升,因此可以在导入实际模块之前和之后执行任何代码。

在我的情况下,我需要测试一些功能并将节点环境设置为与“test”不同的值,并且由于我只需要在导入期间进行这种替换,因此在返回实际模块之前将其值设置回“test”。当然,这可能因用例而异-这只是在导入完成之前和之后执行代码的示例。我还尝试使用特定模块字段的getter,它也起作用了。

import ApiBase from "../ApiBase";

jest.mock('../ApiBase', () => {
    process.env.NODE_ENV = '';
    const actual = jest.requireActual('../ApiBase');
    process.env.NODE_ENV = 'test';
    return actual;
});


1
我能够通过创建一个 setup.js 文件,在 window 对象上添加属性:
global.propertyA = () => {};
global.nestedPropertyB = {
  propertyC: () => {}
};

并在我的 jest.config.js 文件中的 setupFilesAfterEnv 中设置文件:

module.exports = {
  setupFilesAfterEnv: ['<rootDir>/tests/js/setup.js']
}

0
尝试给global.window赋值。
就像这样:
delete global.window.myValue;
global.window = Object.create(window);
global.window.myValue = {
    nestedValue: 'someValue',
};

这可以在 jest.setup.js 中完成,但你也可以在 foo.test.js 中定义这些值。


这对我不起作用。我更新了我的问题,并附上了错误的堆栈跟踪。我简直不敢相信做我想做的事情这么困难,肯定是我漏掉了什么。 - Daniel Reina
你在使用什么测试环境?是 Jsdom/浏览器还是 Node? - christianeide
我没有明确定义测试环境,所以我假设我正在使用默认的jsdom。 - Daniel Reina
如果您尝试执行 console.log(global.window); ,它会记录整个 global.window 对象吗?如果不是,我猜测您的 jest.config.js 设置了 testEnvironment 为非 jsdom。 - christianeide
是的,它记录了一个巨大的对象,其中包含我能够识别为窗口对象一部分的属性,以及许多我不认识的其他属性。 - Daniel Reina

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接