仅模拟模块中的一个功能,但保留原始功能

spe*_*.sm 72 javascript unit-testing jestjs

我只想模拟模块中的单个函数(名为导出),但保持模块的其余功能完好无损。

使用jest.mock('package-name')使所有导出的函数模拟,这是我不想要的。

我尝试将命名导出传播回模拟对象...

import * as utils from './utilities.js';

jest.mock(utils, () => ({
  ...utils
  speak: jest.fn(),
}));

Run Code Online (Sandbox Code Playgroud)

但得到这个错误:

的模块工厂jest.mock()不允许引用任何范围外的变量。

小智 113

这个答案的亮点是jest.requireActual(),这是一个非常有用的实用程序,可以开玩笑说“嘿保持每个原始功能完好无损并导入它们”。

jest.mock('./utilities.js', () => ({
  ...jest.requireActual('./utilities.js'),
  speak: jest.fn(),
}));
Run Code Online (Sandbox Code Playgroud)

让我们再看一个常见的场景,你正在使用酶 ShallowWrapper 并且它不适合 useContext() 钩子,那么你打算怎么做?虽然我确定有多种方法,但这是我喜欢的一种:

import React from "react";

jest.mock("react", () => ({
  ...jest.requireActual("react"), // import and retain the original functionalities
  useContext: jest.fn().mockReturnValue({foo: 'bar'}) // overwrite useContext
}))
Run Code Online (Sandbox Code Playgroud)

这样做的好处是您仍然可以import React, { useContext } from "react"在原始代码中使用 ,而无需担心将它们转换为React.useContext(),就像您使用jest.spyOn(React, 'useContext')

  • 它对我不起作用。```jest.mock``` 和 ```jest.requireActual``` 可以工作,但是当也尝试模拟其中一个函数时,我的代码不断调用原始实现 (24认同)
  • 这在 Jest 27 中不再起作用:“扩展类型只能从对象类型创建”。 (7认同)
  • @ypicard你必须首先将`jest.requireActual('./myModule')`存储在变量中,然后你可以在变量上使用扩展运算符。https://jestjs.io/docs/jest-object#jestrequireactualmodulename (7认同)
  • 当使用索引文件存储许多没有默认导出的组件时,这对我不起作用。我收到“类型错误:无法读取未定义的属性‘默认’” (5认同)
  • 我和@KubaK有同样的问题。我的代码不断调用原始代码而不是工厂中给出的模拟。 (2认同)
  • @Popsicle 与我相同,我的代码不断调用原始变量,而不是模拟变量。 (2认同)

spe*_*.sm 34

最直接的方法是使用jest.spyOn然后.mockImplementation()。这将允许模块中的所有其他函数继续按照定义的方式工作。

对于包裹:

import axios from 'axios';

jest.spyOn(axios, 'get');
axios.get.mockImplementation(() => { /* do thing */ });
Run Code Online (Sandbox Code Playgroud)

对于具有命名导出的模块:

import * as utils from './utilities.js';

jest.spyOn(utils, 'speak');
utils.speak.mockImplementation(() => { /* do thing */ });
Run Code Online (Sandbox Code Playgroud)

文档在这里:https : //jestjs.io/docs/en/jest-object#jestspyonobject-methodname

  • 如果在我的测试文件中调用该函数,则此方法有效。但是,如果该函数在另一个文件中调用/导入,它将不起作用。有什么想法吗? (16认同)
  • 我认为这个解决方案比需要扩展语法更优雅。此外,您可以在`spyOn`调用本身期间分配间谍函数,例如:`const talkSpy = jest.spyOn(utils, "speak");`并稍后调用它:`speakSpy.mockImplementation(() => { /* stuff */ });` (2认同)

Ric*_*ler 17

jest.requireActual内部jest.mock似乎是可行的方法,但是我需要添加代理而不是对象传播,以防止Cannot read properties of undefined (reading ...)在某些导入场景中可能发生的类型错误。

这是最终结果:

jest.mock('the-module-to-mock', () => {
  const actualModule = jest.requireActual('the-module-to-mock')

  return new Proxy(actualModule, {
    get: (target, property) => {
      switch (property) {
        // add cases for exports you want to mock
        // 
        case 'foo': {
          return jest.fn() // add `mockImplementation` etc
        }
        case 'bar': {
          return jest.fn()
        }
        // fallback to the original module
        default: {
          return target[property]
        }
      }
    },
  })
})
Run Code Online (Sandbox Code Playgroud)


小智 8

对我来说这有效:

const utils = require('./utilities.js');
...
jest.spyOn(utils, 'speak').mockImplementation(() => jest.fn());
Run Code Online (Sandbox Code Playgroud)

  • 如果不直接从测试套件中调用 `speak()`,则不会!如果测试调用一个调用 `speak()` 本身的函数,这种安排就会失败! (8认同)

kun*_*aba 6

我采纳了 Rico Kahler 的答案并创建了这个通用函数:

function mockPartially(packageName: string, getMocks: (actualModule: any) => any) {
  jest.doMock(packageName, () => {
    const actualModule = jest.requireActual(packageName);
    const mocks = getMocks(actualModule);

    return new Proxy(actualModule, {
      get: (target, property) => {
        if (property in mocks) {
          return mocks[property];
        } else {
          return target[property];
        }
      },
    });
  });
}

Run Code Online (Sandbox Code Playgroud)

你可以像这样使用它来模拟 lodash:

mockPartially('lodash', (_actualLodash) => { //sometimes you need the actual module
   return {
      'isObject': () => true, //mock isObject
      'isArray': () => true // mock isArray
   }
});
Run Code Online (Sandbox Code Playgroud)