Jest: 如何模拟特定的模块功能?

创建于 2016-04-25  ·  116评论  ·  资料来源: facebook/jest

我正在努力解决我认为应该既简单又明显的事情,但无论出于何种原因,我都无法弄清楚。

我有一个模块。 它导出多个函数。 这是myModule.js

export function foo() {...}
export function bar() {...}
export function baz() {...}

我取消模拟模块进行测试。

jest.unmock('./myModule.js');

但是,我需要模拟foo ,因为它会对我的后端进行 ajax 调用。 我希望这个文件中的每个函数都保持未模拟,期待foo ,我想被模拟。 并且函数barbaz在内部调用foo ,所以当我的测试调用bar() ,未模拟的bar将调用嘲笑foo

它出现在 jest 文档中,调用unmockmock对整个模块进行操作。 如何模拟特定功能? 随意将我的代码分解成单独的模块以便正确测试它们是荒谬的。

最有用的评论

你可以做:

jest.unmock('./myModule.js');

const myModule = require('myModule');
myModule.foo = jest.fn();

http://facebook.github.io/jest/docs/api.html#mock -functions

所有116条评论

经过深入分析,似乎 jest-mock 为整个模块生成了一个 AST,然后使用该 AST 创建一个符合原始导出的模拟模块: https :

其他测试框架,比如 Python 的 mock(https://docs.python.org/3/library/unittest.mock-examples.html),让你模拟特定的功能。 这是一个基本的测试概念。

我强烈推荐能够模拟模块的一部分。 我认为应该将 jest-mock 更改为有条件地忽略模拟的导出,并参考原始实现。

你可以做:

jest.unmock('./myModule.js');

const myModule = require('myModule');
myModule.foo = jest.fn();

http://facebook.github.io/jest/docs/api.html#mock -functions

我认为您对require工作方式有根本的误解。 当您调用require() ,您不会获得模块的实例。 你会得到一个引用模块函数的对象。 如果您覆盖所需模块中的值,您自己的引用将被覆盖,_但实现保留原始引用_。

在您的示例中,如果您调用myModule.foo() ,是的,您将调用模拟版本。 但是如果你调用myModule.bar() ,它在内部调用foo() ,它引用的foo _不是你覆盖的版本_。 如果你不相信我,你可以测试一下。

因此,您描述的示例不足以解决我遇到的问题。 你知道我不知道的事情吗?

@cpojer

我相信我很清楚这一点。 然而 babel 编译模块的方式并没有让这更容易理解,我理解你的困惑。 我不知道这在带有模块的真实 ES2015 环境中会如何表现,主要是因为现在不存在这样的环境(可能除了 Chrome Canary 的最新版本,我还没有尝试过)。 为了解释发生了什么,我们必须查看 babel 代码的编译输出。 它看起来像这样:

var foo = function foo() {};
var bar = function bar() { foo(); };

exports.foo = foo;
exports.bar = bar;

在这种情况下,您不能模拟 foo 确实是正确的,我为没有正确阅读您的初始问题而道歉,但是它没有对foo调用方式做出任何假设,所以我假设它是exports.foo() 。 在 JavaScript 中通过在需要一个模块之后模拟一个函数来支持上述内容是不可能的——(几乎)没有办法检索 foo 引用的绑定并修改它。

但是,如果您将代码更改为:

var foo = function foo() {};
var bar = function bar() { exports.foo(); };

exports.foo = foo;
exports.bar = bar;

然后在您的测试文件中执行以下操作:

var module = require('../module');
module.foo = jest.fn();
module.bar();

它会按预期工作。 这就是我们在不使用 ES2015 的 Facebook 所做的。

虽然 ES2015 模块可能对其导出的内容具有不可变的绑定,但 babel 现在编译为的底层编译代码并没有强制执行任何此类约束。 我认为目前没有办法在具有本机支持的模块的严格 ES2015 模块环境中完全支持您的要求。 jest-mock工作方式是它独立运行模块代码,然后检索模块的元数据并创建模拟函数。 同样,在这种情况下,它无法修改foo的本地绑定。 如果您确实有关于如何有效实现这一点的想法,请在此处贡献或通过拉取请求。 我想提醒你,我们有这个项目的行为准则,你可以在这里阅读: https :

在您的示例中,正确的解决方案不是模拟 foo,而是模拟 foo 正在调用的更高级别的 API(例如 XMLHttpRequest 或您使用的抽象)。

@cpojer感谢您的详细解释。 如果我用我的语言冒犯了你,我很抱歉,我的工程写作效率很高,我想尽快表达我的观点。 为了正确看待事情,我花了 5 个小时的时间试图理解这个问题并写了 2 条详细的评论,然后你用一条简短的消息将其关闭,完全错过了我的两个陈述的重点。 这就是为什么我的下一条消息说你有一个“根本性的误解”,因为要么 1)你不理解我的意思,要么 2)你不理解require() ,幸好这是选项 1。

我会考虑我的问题的可能解决方案,为了解决这个问题,我现在模拟了一个较低级别的 API,但肯定应该有一种方法可以直接模拟该函数,因为这将非常有用。

我同意能够做到这一点很有用,但是如果没有(可能很慢)预先进行静态分析,JS 中没有好的方法可以做到这一点:(

@cpojer :我不确定 5 个月后加入这里是否

根据您上面的建议,我这样做是为了从同一模块中的另一个函数中模拟一个函数:

jest.unmock('./someModule.js');
import someModule from './someModule.js';

it('function1 calls function 2', () => {
    someModule.function2 = jest.fn();

    someModule.function1(...);

    expect(someModule.function2).toHaveBeenCalledWith(...);
});

这适用于一个测试,但我还没有找到一种方法来以与一个it(...);块隔离的方式来完成它。 如上所述,它会影响每个测试,这使得很难在另一个测试中测试真正的function2 。 有小费吗?

您可以在beforeEach的函数上调用.mockClear或者如果您使用的是 Jest 16,则调用jest.clearAllMocks()

嘿@cpojer! 我正在使用 Jest 16。 jest.clearAllMocks()someModule.function2.mockClear()都不适合我。 它们仅在模拟是整个模块时才起作用,而不是导入模块的功能。 在我的项目中,该函数在后续测试中仍然被模拟。 如果这不是预期的,我会看看我是否可以在一个小示例项目中复制并创建一个新问题。 好主意?

@cpojer -

在您的示例中,正确的解决方案不是模拟 foo,而是模拟 foo 正在调用的更高级别的 API(例如 XMLHttpRequest 或您使用的抽象)。

我是 Jest 的新手,我也遇到了类似的问题。 我正在使用axios ,它在引擎盖下使用XMLHttpRequest ,我不想模拟axios ,而是模拟实际的XMLHttpRequest 。 看来我必须自己实现它的方法,就像这样。 这是正确的方法吗?

谢谢!

是的,这样的事情应该让你走上正确的道路! :) 使用jest.fn作为更好的 API,不过 :D

@cpojer关于您在此处的评论: https :

你会如何用 ES2015 做到这一点?

// myModyle.js
export foo = (string) => "foo-" + string
export bar = (string2) => foo(string2)

// myModule.test.js
var module = require('./myModule');

// how do I mock foo here so this test passes?
expect(bar("hello")).toEqual("test-hello")

对于遇到此问题寻找解决方案的任何人,在一个文件中导出许多常量/函数并将它们导入到我正在测试的文件中时,以下内容似乎对我有用

`` javascript function mockFunctions() { const original = require.requireActual('../myModule'); return { ...original, //Pass down all the exported objects test: jest.fn(() => {console.log('I didnt call the original')}), someFnIWantToCurry: {console.log('I will curry the original') return jest.fn((...args) => original.someFnIWantToCurry(...args)}), } jest.mock('../myModule', () => mockFunctions()); const storage = require.requireMock('../myModule');

@ainesophaur ,不确定我在这里做错了什么。 但它似乎不起作用
我目前在 jest 18.1(和 create-react-app 0.9.4)

...<codes from comment above>..

// Let's say the original myModule has a function doSmth() that calls test()
storage.doSmth();
expect(storage.test).toHaveBeenCalled();

然后测试将失败:

expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called.

@huyph你必须模拟你的 doSmth 方法和你的测试方法,以便 jest 测试它是否被调用。 如果您可以提供模拟代码的片段,我可以检查出什么问题

@ainesophaur ...嗯。 我认为你上面的代码是用来模拟test()方法的? 这部分: test: jest.fn(() => {console.log('I didnt call the original')}),

@ainesophaur我也试过你的代码。 但它对我不起作用。 它从不执行模拟功能。 所以,期望永远不会得到满足。

我认为这是上述要求工作方式所固有的......我希望有一个解决方案。

@cpojer是否有关于部分

@rantonmattei@huyph我必须查看您的模拟定义和正在运行的测试的片段。 您必须在需要/导入实际实现文件之前定义模拟。 自从我使用 JEST 以来已经有一段时间了,但我确实记得我最终得到了它来模拟我需要的一切,无论是 node_modules 库还是我的应用程序中的文件。 我在 ATM 上的时间有点短,但这里是我使用 Jest 工作的项目中的一些测试。

从依赖中模拟文件

这个例子中的实际函数定义是由 react-native 完成的。我在嘲笑文件“react-native/Libraries/Utilities/dismissKeyboard.js”

这是__mocks__/react-native/Libraries/Utilities/dismissKeyboard.js下的模拟文件

function storeMockFunctions() {
  return jest.fn().mockImplementation(() => true);
}
jest.mock('react-native/Libraries/Utilities/dismissKeyboard', () => storeMockFunctions(), { virtual: true });
const dismissKeyboard = require('react-native/Libraries/Utilities/dismissKeyboard');
exports = module.exports = storeMockFunctions;

我找不到我用于上面的测试文件,但它类似于 require 模块,jest 会在 __mocks__ 中找到它,然后我可以做类似的事情

expect(dismissKeyboard.mock.calls).toHaveLength(1);

模拟您控制的文件
实际功能定义

export const setMerchantStores = (stores) => storage.set('stores', stores);

使用模拟测试文件

const { storeListEpic, offerListEpic } = require('../merchant');

function storeMockFunctions() {
  const original = require.requireActual('../../common/Storage');
  return {
    ...original,
    setMerchantStores: jest.fn((...args) => original.setMerchantStores(...args)),
    setMerchantOffers: jest.fn((...args) => original.setMerchantOffers(...args)),
  };
}
jest.mock('../../common/Storage', () => storeMockFunctions());
import * as storage from '../../common/Storage';

afterEach(() => {
  storage.setMerchantStores.mockClear();
});

it('handle storeListEpic type STORE_LIST_REQUEST -> STORE_LIST_SUCCESS', async () => {
  const scope = nock('http://url')
  .get('/api/merchant/me/stores')
  .reply(200, storeData);
  const result = await storeListEpic(ActionsObservable.of(listStores())).toPromise();
  expect(storage.setMerchantStores.mock.calls).toHaveLength(1);
  expect(await storage.getMerchantStores()).toEqual({ ids: storesApiData.result, list: storesApiData.entities.store});
});

感谢分享@ainesophaur。 我仍然无法让它与 jest 18.1 一起使用。 这是我的代码:

it('should save session correctly', () => {

  function mockFunctions() {
    const original = require.requireActual('./session');
    return {
      ...original,
      restartCheckExpiryDateTimeout: jest.fn((() => {
        console.log('I didn\'t call the original');
      })),
    }
  }

  jest.mock('./session', () => mockFunctions());
  const mockSession = require('./session');

  // NOTE: saveSession() will call the original restartCheckExpiryDateTimeout() instead of my
  // mock one. However, mockSession.restartCheckExpiryDateTimeout() does call the mock one
  mockSession.saveSession('', getTomorrowDate(), 'AUTH');

  // mockSession.restartCheckExpiryDateTimeout(); // does print out "I didn't call the original"

  expect(mockSession.restartCheckExpiryDateTimeout).toHaveBeenCalled();
});

session.js

export function saveSession(sessionId, sessionExpiryDate, authToken) {
  ....
  restartCheckExpiryDateTimeout(sessionExpiryDate);
  ...
}
....

export function restartCheckExpiryDateTimeout(...) {
....
}

我找不到解决此问题的方法。 请问这个可以重开吗? @cpojer

@huyph您执行导出saveSession方式将调用本地定义的restartCheckExpiryDateTimeout而不是通过模块并调用module.restartCheckExpiryDateTimeout - 因此您嘲笑module.restartCheckExpiryDateTimeout不会被saveSession检测到,因为saveSession正在调用实际定义的restartCheckExpiryDateTimeout函数。

我将saveSession分配给一个常量,然后执行saveSession.restartCheckExpiryDateTimeout = () => {...logic} 。 .then 从saveSession.saveSession中调用saveSession.restartCheckExpiryDateTimeout而不是restartCheckExpiryDateTimeout 。 导出您的新 const 而不是实际的函数saveSession然后定义您的方法。 然后当你调用你的someVar.saveSession()它会在内部调用saveSession.restartCheckExpiryDateTimeout()现在被嘲笑。

我应该补充说restartCheckExpiryDateTimeout()是一个导出函数。 不是saveSession()内的本地定义函数......(更新了我上面的评论)。 在这种情况下,我认为module.saveSession()应该调用正确的module.restartCheckExpiryDateTimeout()被嘲笑。

但是我会尝试一下你上面的建议。 将saveSession()restartCheckExpiryDateTimeout()到另一个常量。 谢谢

我知道它没有在 saveSession 的范围内定义。 保存会话是
在父作用域中调用兄弟方法。 我遇到过很多次
我的建议对它有用

2017 年 5 月 8 日晚上 8:38,“Huy Pham”通知@ github.com 写道:

我应该补充说,restartCheckExpiryDateTimeout() 是导出的
功能。 不是 saveSession() 中的本地定义函数...

不过,我会给你上面的建议。 谢谢


你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/facebook/jest/issues/936#issuecomment-300029003或静音
线程
https://github.com/notifications/unsubscribe-auth/AEeBdsmpOOmzvcUHB3D_-Z7MChIzt10Pks5r37WYgaJpZM4IPGAH
.

刚试过..我发现:

这不起作用:(即仍然调用原始的restartCheckExpiryDateTimeout())

export session = {
   saveSession: () => {
      session.restartCheckExpiryDateTimeout();
   },
   restartCheckExpiryDateTimeout: () => {},
}

这有效:(即模拟 restartCheckExpiryDateTimeout() 被调用)。 区别在于使用function()代替箭头形式,以及使用this.代替session.

export session = {
   saveSession: function() {
      this.restartCheckExpiryDateTimeout();
   },
   restartCheckExpiryDateTimeout: () => {},
}

开玩笑地转译这些代码可能是一个问题......

尝试将它们导出为类对象而不是 pojo。 我相信
转译器确实以不同的方式提升变量。 我们将开始工作
测试,我保证......我参与这个项目已经大约半年了
那个用玩笑,但我记得这个问题,我最终记得
找到解决办法。

2017 年 5 月 9 日上午 12:53,“Huy Pham”通知@ github.com 写道:

刚试过..我发现:

这不起作用:(即原来的 restartCheckExpiryDateTimeout() 是
仍然被调用)

导出会话 = {
保存会话:() => {
session.restartCheckExpiryDateTimeout();
},
restartCheckExpiryDateTimeout: () => {},
}

这不起作用:(即原来的 restartCheckExpiryDateTimeout() 是
仍然被调用)

导出会话 = {
保存会话:函数(){
this.restartCheckExpiryDateTimeout();
},
restartCheckExpiryDateTimeout: () => {},
}

开玩笑地转译这些代码可能是一个问题......


你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/facebook/jest/issues/936#issuecomment-300060975或静音
线程
https://github.com/notifications/unsubscribe-auth/AEeBdrRQExycPYiGtvm7qYi5G87w6b6Oks5r3_FlgaJpZM4IPGAH
.

@sorahn同样的问题。 es6 + babel ,如何模拟?
@cpojer是否意味着es6 + babelexport const function xx() {} ,导出多个函数, Jest无法模拟调用的模块(文件)中的函数通过同一模块(文件)中的其他功能? 我测试了一下,看来我是对的。 仅对于commonjs模式,Jest 可以成功模拟该函数,就像您的示例一样。

@ainesophaur不工作。

模块:

export const getMessage = (num: number): string => {
  return `Her name is ${genName(num)}`;
};

export function genName(num: number): string {
  return 'novaline';
}

测试:

function mockFunctions() {
  const original = require.requireActual('../moduleA');
  return {
    ...original,
    genName: jest.fn(() => 'emilie')
  }
}
jest.mock('../moduleA', () => mockFunctions());
const moduleA = require('../moduleA');

describe('mock function', () => {

  it('t-0', () => {
    expect(jest.isMockFunction(moduleA.genName)).toBeTruthy();
  })

  it('t-1', () => {

    expect(moduleA.genName(1)).toBe('emilie');
    expect(moduleA.genName).toHaveBeenCalled();
    expect(moduleA.genName.mock.calls.length).toBe(1);
    expect(moduleA.getMessage(1)).toBe('Her name is emilie');
    expect(moduleA.genName.mock.calls.length).toBe(2);

  });

});

测试结果:

FAIL  jest-examples/__test__/mock-function-0.spec.ts
  ● mock function › t-1

    expect(received).toBe(expected)

    Expected value to be (using ===):
      "Her name is emilie"
    Received:
      "Her name is novaline"

      at Object.it (jest-examples/__test__/mock-function-0.spec.ts:22:35)
      at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)

  mock function
    ✓ t-0 (1ms)
    ✕ t-1 (22ms)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        0.215s, estimated 1s

看看我上面的最后几条评论。 特别是最后一个。 您的
导出的方法正在调用本地范围的同级方法与
实际导出的方法(这是您的模拟所在的位置)

2017 年 5 月 31 日凌晨 2 点,“novaline”通知@github.com 写道:

@ainesophaur https://github.com/ainesophaur不工作。

模块:

export const getMessage = (num: number): string => {
返回Her name is ${genName(num)} ;
};
导出函数 genName(num: number): string {
返回 '​​novaline';
}

测试:

函数模拟函数(){
const original = require.requireActual('../moduleA');
返回 {
...原来的,
genName: jest.fn(() => 'emilie')
}
}jest.mock('../moduleA', () => mockFunctions());const moduleA = require('../moduleA');
描述('模拟函数',()=> {

it('t-0', () => {
期望(jest.isMockFunction(moduleA.genName)).toBeTruthy();
})

it('t-1', () => {

expect(moduleA.genName(1)).toBe('emilie');
expect(moduleA.genName).toHaveBeenCalled();
expect(moduleA.genName.mock.calls.length).toBe(1);
expect(moduleA.getMessage(1)).toBe('Her name is emilie');
expect(moduleA.genName.mock.calls.length).toBe(2);

});

});

测试结果:

失败 jest-examples/__test__/mock-function-0.spec.ts
● 模拟功能 › t-1

expect(received).toBe(expected)

Expected value to be (using ===):
  "Her name is emilie"
Received:
  "Her name is novaline"

  at Object.it (jest-examples/__test__/mock-function-0.spec.ts:22:35)
  at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)

模拟功能
✓ t-0 (1ms)
✕ t-1 (22ms)

测试套件:1 个失败,总共 1 个
测试:1 次失败,1 次通过,总共 2 次
快照:共 0 个
时间:0.215s,估计1s


你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/facebook/jest/issues/936#issuecomment-305091749或静音
线程
https://github.com/notifications/unsubscribe-auth/AEeBdv6SafXlTtKo3DNeFWhbL6gV9l0Gks5r_QHjgaJpZM4IPGAH
.

@ainesophaur :我尝试了export class Session { } 。 它对我不起作用。

唯一对我有用的方法是在我上面的评论中:其中使用function语法而不是箭头() => 。 这里:

export const session = {
   saveSession: function() {
      this.restartCheckExpiryDateTimeout();
   },
   restartCheckExpiryDateTimeout: () => {},
}

这是在 Jest 20.0.3 上

我所做的是为函数创建一个 const 包装器,然后导出该包装器(例如 export const fns)。 然后在模块内部使用 fns.functionName,然后我可以 jest.fn() fns.functionName 函数

当我们编写用 typescript 编写的用户定义模块的模拟函数时,当我们调用模拟函数时,覆盖率报告中涵盖的原始函数是因为我们正在调用该函数的模拟版本。

我有 2 个最初在测试中导入的函数
import { getCurrentDate, getStartEndDatesForTimeFrame } from ./../_helpers/date';
如您所见, getStartEndDatesForTimeFrame依赖于getCurrentDate 。 通过以下设置, getCurrentDate测试运行良好并使用模拟版本。 另一方面,由于某种原因, getStartEndDatesForTimeFrame测试不使用模拟的getCurrentDate而是原始实现,因此我的测试失败。 我尝试了许多不同的设置(例如Date.now = jest.fn(() => "2017-11-16T20:33:09.071Z");但无法使其工作。有任何想法吗?

export const getCurrentDate = () => new Date();
export const getStartEndDatesForTimeFrame = (timeFrame) => {
  ...
  const todayDate = getCurrentDate();
  ...
  switch (timeframe) {
    case TimeFrames.TODAY:
      console.log(todayDate); // this always prints the real value in tests instead of the mocked one
      start = new Date(todayDate.getFullYear(), todayDate.getMonth(), todayDate.getDate(), 0, 0, 0);
      end = new Date(
        todayDate.getFullYear(),
        todayDate.getMonth(),
        todayDate.getDate(), 23, 59, 59,
      );
      break;
  ...
  return { start: start.toISOString(), end: end.toISOString() }
};
function mockFunctions() {
  const original = require.requireActual('../../_helpers/date');
  return {
    ...original,
    getCurrentDate: jest.fn(() => '2017-11-16T20:33:09.071Z'),
  }
}
jest.mock('../../_helpers/date', () => mockFunctions());
const dateModule = require.requireMock('../../_helpers/date');

describe('getCurrentDate', () => {
  it('returns the mocked date', () => {
    expect(dateModule.getCurrentDate()).
      toBe('2017-11-16T20:33:09.071Z'); // this works well and returns the mocked value
  });
});

describe('getStartEndDatesForTimeFrame', () => {
  it('returns the start and end dates for today', () => {
    expect(dateModule.getStartEndDatesForTimeFrame('today')).toEqual(
      { 'start': '2017-11-15T23:00:00.000Z', 'end': '2017-11-16T22:59:59.000Z' }
    ); // this one uses the original getCurrentDate instead of the mocked one :(
  });
});

所以getStartEndDatesForTimeFrame失败,因为它使用当前时间而不是模拟时间。

我已经按照@ainesophaur的建议设法使其工作 - 通过导出对象中的所有函数并调用这些导出对象的方法而不是本地范围的同级方法:

// imageModel.js
const model = {
  checkIfImageExists,
  getImageUrl,
  generateImagePreview
}
export default model

async function checkIfImageExists(...) {}
async function getImageUrl() {}
async function generateImagePreview() {
  // I am calling it as `model` object's method here, not as a locally scoped function
  return model.getImageUrl(...)
}

// imageModel.test.js
import imageModel from './imageModel'

test('should mock getImageUrl called within the same file', async () => {
  imageModel.getImageUrl = jest.fn().mockReturnValueOnce(Promise.resolve())

  await imageModel.generateImagePreview()

  expect(imageModel.getImageUrl).toBeCalled()
})

@miluoshi这也是我能够做到的唯一方法。 当我们使用这种方法时,是否有任何性能损失或类似的东西? 更改代码以便您可以对其进行测试似乎是“错误的”。

我真的很想要一种写法:
jest.mock('src/folder/file.func, () => {return 'whatever i want'})

这里的关键部分是.func

@miluoshi @Rdlenke如果您的代码包含命名导出,您还可以import * as model然后覆盖model.generateImagePreview = jest.fn(() => Promise.resolve);

你将如何用 sinon 测试它? 就像之前提到的(见 https://github.com/facebook/jest/issues/936#issuecomment-214939935),ESM 的工作方式使得在func1内模拟func2是不可能的,所以我不一定称其为基本的。

也许可以编写一个 babel mod 来读取任何“testImport”函数
并重写代码以导出模块中的函数
试运行?

2017 年 12 月 18 日星期一下午 5:00,Jim Moody [email protected]写道:

你说得对@SimenB https://github.com/simenb ,我改变了一些东西
在我切换到 Sinon 之间的测试中,它看起来像是通过了。
当我恢复它时,它仍然无法正常工作。 我想这不是问题
这已经解决了。


你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/facebook/jest/issues/936#issuecomment-352488400或静音
线程
https://github.com/notifications/unsubscribe-auth/AQRY9a5-s2_bjCWKNw5WiAJW-JeBf8W3ks5tBpoygaJpZM4IPGAH
.

——

达伦·克雷斯韦尔
合约开发商 | 开发商有限公司
电子邮箱: [email protected]
电话:
网站: http :

在打印这封电子邮件之前请考虑环境
警告:计算机病毒可以通过电子邮件传播。 收件人
应检查此电子邮件和任何附件是否存在病毒。
Develer Limited 对任何病毒造成的任何损害不承担任何责任
通过这封电子邮件传送。 无法保证电子邮件传输
安全或无错误,因为信息可能被拦截、损坏、丢失,
已损坏、迟到或不完整,或包含病毒。 发送方
因此不承担任何错误或遗漏的责任
由于电子邮件传输而出现的此消息的内容。

警告:尽管 Develer Limited 已采取合理的预防措施
确保此电子邮件中不存在病毒,公司不能接受
因使用此电子邮件或
附件。

Develer Limited 是一家在英格兰和威尔士注册的有限公司。 |
公司注册号09817616 | 注册办事处:SUITE 1 SECOND
EVERDENE HOUSE, DEANSLEIGH ROAD, BOURNEMOUTH, 英国, BH7 7DU

感谢@ainesophaur的解决方法。

如果有人发现非异步工作示例有用,这是我的:

//reportError.js
const functions = {};

functions._reportToServer = (error, auxData) => {
  // do some stuff
};

functions.report = (error, auxData = {}) => {
  if (shouldReportToServer()) {
    functions._reportToServer(error, auxData);
  }
};
export default functions;

// reportError.jest.js
import reportError from 'app/common/redux/lib/reportError';
reportError._reportToServer = jest.fn();

describe('test reportError', () => {
  it('reports ERROR to server as as error', () => {
   reportError.report(new Error('fml'), {});
    expect(reportError._reportToServer).toHaveBeenCalledTimes(1);
  });
});

@jim-moody 如果我正确理解了这个问题,这应该适用于您的示例:

const spyOnExampleFunc2 = jest.spyOn(example, 'func2');
example.func1();
expect(spyOnExampleFunc2).toBeCalled();

(如果函数被导出为常量,这 _only_ 有效,就像你的例子一样)

@dinvlad我的英雄!

根据@dinvlad的回答,我认为在jest 对象页面上添加、通过示例显示或将以下模拟相关文档链接到Mock 函数页面可能是对

  • jest.isMockFunction(fn)
  • jest.genMockFromModule(moduleName)
  • jest.mock(模块名称,工厂,选项)
  • jest.unmock(模块名称)
  • jest.doMock(moduleName, factory, options)
  • jest.dontMock(moduleName)

我的用例是,作为 jest 的新用户,我正在将一些 mocha + sinon.js 代码迁移到 jest。 我已经有了间谍和期望,所以我认为这很容易。 但是在阅读了这个线程并阅读了关于 Mock 函数的 jest 文档后,我得到的印象是,以这种方式使用 jest 可能需要重写我的测试或详细了解 ESM 或 Babel ......或其他混淆。

感谢 Jest - 它使我的测试更易于编写/理解并更快地执行。 :)

非常欢迎公关澄清文档! 🙂

要仅模拟具有 ES 模块语法的特定模块,您可以使用require.requireActual恢复原始模块,然后覆盖要模拟的模块:

import { foo } from './example';

jest.mock('./example', () => (
  ...require.requireActual('./example'),
  foo: jest.fn()
));

test('foo should be a mock function', () => {
  expect(foo('mocked!')).toHaveBeenCalledWith('mocked!');
});

感觉颠倒了,但这是我遇到的最简单的方法。 给@joshjg 的帽子提示。

我在长时间的讨论中迷失了方向,我只是有一个问题,无论如何要测试函数的实际实现是否被调用?

据我了解,如果我需要使用jest.fn()它将覆盖原始函数,但如果我不使用它,控制台会给我错误说它必须是jest.fn() function or a spy

我正在尝试测试将传递请求的中间件,因此如果我模拟它,所有逻辑都将丢失并且数据不会传递到下一个中​​间件。 如果我不嘲笑它,通过导入它,无论如何我可以测试这个函数是否被调用?

你可以使用jest.spyOn ,也许? 默认情况下,它调用底层函数

感谢您的帮助,我试过了,但测试表明它从来没有被调用过,即使它被调用了,因为我把 console.log 和它打印出来了

测试文件

import errorHandler from '../../controller/errorHandler'

describe('auth test', () => {
  describe('test error: ', () => {
    const test1 = jest.spyOn(errorHandler, 'handleClientError')
    test('should return 400', (done) => {
      request(app)
      .post('/auth/error')
      .then((res) => {
        expect(res.statusCode).toBe(400)
        expect(test1).toBeCalled()
        done()
      })
    })

错误处理程序

module.exports = {
  handleClientError () {
    console.log('err')
  }
}

安慰

console.log src/controller/errorHandler.js:10
      err

  ● auth test › test error:  ›  should return 400

    expect(jest.fn()).toBeCalled()

    Expected mock function to have been called.

      18 |         expect(res.statusCode).toBe(400)
    > 19 |         expect(test1).toBeCalled()
      20 |         done()
      21 |       })
      22 |     })

函数是handleClientError还是logError

@WangHansen从你的例子中你的代码应该是expect(errorHandler.handleClientError).toBeCalled() // > true

@WangHansen.mockImplementation()到你的jest.spyOn()吗? 作为来自 Jasmine 的人,我发现这个技巧对于实现与 Jasmine 的间谍相同的功能至关重要。 例如

const mockModuleFunction = jest
  .spyOn(module, 'function')
  .mockImplementation(() => 'hello');
...
expect(mockModuleFunction.mock).toBeCalled();

如果您_不_使用mockImplementation() ,则jest.spyOn()会生成一个_not_ 模拟(afaiu)的对象,并且它实际上遵循本机实现。 如果您必须保留本机实现,也许值得使用

const moduleFunction = module.function;
jest.spyOn(module, 'function').mockImplementation(moduleFunction);
...

不确定这是必要的,但相当确定它_应该_工作。

回到最初的要求...

难道你不能在导入 * 周围包装一个代理吗? 例如

从'./myfile.js'导入*作为测试;

常量处理程序 = {
/** 拦截:获取属性 */
获取(目标,propKey,接收器){
console.log( GET ${propKey} );
返回 123;
},

/** Intercepts: checking whether properties exist */
has(target, propKey) {
    console.log(`HAS ${propKey}`);
    return true;
}};

const p = 新代理(测试);

2018 年 1 月 30 日,星期二,下午 4:24,Denis Loginov通知@github.com
写道:

@WangHansen https://github.com/wanghansen你能补充一下吗
.mockImplementation() 到你的 jest.spyOn()? 作为一个来自
Jasmine,我发现这个技巧对于实现与
茉莉的间谍。 例如

const mockModuleFunction = 开玩笑
.spyOn(模块,'功能')
.mockImplementation(() => 'hello');...expect(mockModuleFunction.mock).toBeCalled();

如果你使用 mockImplementation(),那么 jest.spyOn() 会产生一个
不是模拟(afaiu)的对象,它实际上遵循本机
执行。 如果您必须保留本机实现,也许是
值得使用

const moduleFunction = module.function;jest.spyOn(module, 'function').mockImplementation(moduleFunction);...

不确定这是必要的,但相当确定它应该有效。


你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/facebook/jest/issues/936#issuecomment-361648414或静音
线程
https://github.com/notifications/unsubscribe-auth/AQRY9VXyHNYatwOOY6EV637WGQH9k5Plks5tP0I9gaJpZM4IPGAH
.

——

达伦·克雷斯韦尔
合约开发商 | 开发商有限公司
电子邮箱: [email protected]
电话:
网站: http :

在打印这封电子邮件之前请考虑环境
警告:计算机病毒可以通过电子邮件传播。 收件人
应检查此电子邮件和任何附件是否存在病毒。
Develer Limited 对任何病毒造成的任何损害不承担任何责任
通过这封电子邮件传送。 无法保证电子邮件传输
安全或无错误,因为信息可能被拦截、损坏、丢失,
已损坏、迟到或不完整,或包含病毒。 发送方
因此不承担任何错误或遗漏的责任
由于电子邮件传输而出现的此消息的内容。

警告:尽管 Develer Limited 已采取合理的预防措施
确保此电子邮件中不存在病毒,公司不能接受
因使用此电子邮件或
附件。

Develer Limited 是一家在英格兰和威尔士注册的有限公司。 |
公司注册号09817616 | 注册办事处:SUITE 1 SECOND
EVERDENE HOUSE, DEANSLEIGH ROAD, BOURNEMOUTH, 英国, BH7 7DU

@dinvlad @iampeterbanjo @SimenB再次感谢您的所有帮助,但不幸的是,您建议的方法无效。 我想知道是不是因为函数是以next(err)的形式调用的。 逻辑是,当请求失败时,它将通过调用return next(err)将其传递到errorHandler return next(err) 。 肯定正在调用该函数,因为当我添加console.log ,它正在打印。 但测试表明它从未被称为

@dinvlad我试过你的方法,mockImplementation ,根据jest.spyOn上的官方文档,你只在想要覆盖原始函数时调用它。

@WangHansen是的,您是对的,只有在想要覆盖原始方法时才需要它。 我只是针对这些情况提出一个想法。

它可能对您失败的原因之一是异步性。 如果您的方法使用回调和/或承诺(或异步/等待),那么您需要确保在测试方法终止之前实际执行了您的期望。 有一个特殊的方法expect.assertions(N)来断言。 还要确保您的期望仅在回调/承诺中的代码被调用后才执行。 我相信你已经看过了,但仅供参考, https://facebook.github.io/jest/docs/en/asynchronous.html

不幸的是,在不更改模块 impl 的情况下,不可能像@seibelj描述的那样fns

在我看来,测试不应该驱动业务逻辑的实现方式😕(至少不会达到这个程度)

有没有计划在玩笑中实施这种行为?

你好,
整个讨论有点晚,但通读整个讨论 - 我仍然没有设法做到这一点。
@greypants的有前途的解决方案对我来说并没有真正起作用,因为它仍然调用原始函数。
自本次讨论开始以来,有什么变化吗? 我错过了什么吗?

我稍微调整了@greypants的解决方案,它对我

import Module from './module'
import { getJSON } from './helpers'

jest.mock('./helpers', () =>
  Object.assign(require.requireActual('./helpers'), {
    getJSON: jest.fn()
  })
)

test('should use the mock', async () => {
  getJSON.mockResolvedValue('foo')
  await expect(module().someAsyncThingThatUsesGetJSON()).resolves.toEqual('foo')
})

test('should use the actual module', () => {
  expect(module().someFunctionThatUsesAHelper()).toBe(true)
})

这仍然感觉有点hacky,文档也没有那么有用。 我很感谢 Jest 及其背后的团队,但这似乎是一个常见的用例,至少应该有一个“官方”解决方案。

@sarahdayan ,以及任何可能感兴趣的人 -
我最终使用了 babel-plugin-rewire。
我花了一段时间才找到那个插件,但它是一个足够连贯的解决方案,不会觉得自己很笨拙。

在大多数情况下,我们想从一个模块没有模拟一个或多个功能。 如果您使用 global jest 的模拟系统,您可以使用genMockFromModulerequireActual来实现它。 这是示例:

//  __mocks__/myModule.js
const moduleMock = jest.genMockFromModule('./../myModule');

// in most cases we don't want to mock `someFunction`
moduleMock.someFunction = jest.fn(
  (...args) => require.requireActual('./../myModule').someFunction(...args)
)

module.exports = moduleMock;

此解决方案允许对模块中的其他函数使用模拟,在模拟整个模块时使用someFunction原始实现,还允许使用mockImplementationOncemockImplementation模拟someFunction函数mockImplementation API。

我已阅读上述所有对话,但没有一种解决方案适合我。
如果你还在为这个测试用例寻找解决方案,答案是babel-plugin-rewire ,这个插件是为了解决我们讨论的那个场景案例。
请查看该库,稍后您会感谢我。

所以总结一下上面的整个线程:

  1. 假设您有一个模块m具有函数fgh ,其中gh调用f 。 我们想模拟f以便gh调用模拟而不是真正的f 。 不幸的是,这是不可能直接做到的,除非f总是像cpojer 描述的那样通过exports调用。 如果您的模块在 TypeScript 中使用 ES6 导入/导出语法,这是不可能的(我猜在 Babel 中也是如此)。
  2. 但是,假设我们将f到另一个模块m2 。 然后m将有一个类似import {f} from 'm2'的语句,当gh调用f ,它们实际上是在调用m2.f where m2 = require('./m2') (这就是 Babel/TypeScript 翻译的样子)。 这使得可以像greypantsf 。 换句话说,只有当调用跨越模块边界时注意: greypants 的解决方案现在会产生以下错误消息:“ jest.mock()的模块工厂不允许引用任何范围外的变量 - 无效的变量访问:__assign”。 我怀疑这是 Jest 中的一个错误; 作为解决方法,请使用Object.assign ,如下所示。
  3. 但是,如果不是模拟一两个函数,而是想模拟一两个函数类似 darkowic 的.

(2) 的例子:

~~~js
// 模块 m.js
从 './m2' 导入 {f}
导出函数 g() { return 'f 返回 ' + f(); };

// 模块 m2.js
导出函数 f() { return 'the real f'; }

// 测试.js
import * as m from './m'

jest.mock('./m2', () => Object.assign(
require.requireActual('./m2'), {
f: jest.fn().mockReturnValue('MOCK')
}));

测试('模拟',()=> {
期望(mg()).toEqual('f 返回模拟');
});
~~~

在测试这个时我遇到了#2649:在测试中调用jest.mock没有效果,如果你在全局范围内调用它,那么你不能在其他测试之前unmock 。 很烦人。

谢谢!! @sarahdayan
找这个有一段时间了

如果缺少文档,欢迎 PR 来澄清它们🙂

嗨,大家好!

我玩了一会儿,有以下想法来解决这个问题:

  • 模拟一个模块,但被模拟的模块在原型链中具有原始模块。
  • 提供一种向模拟模块添加属性的方法(这将覆盖原型中的属性)
  • 还提供了一种从模拟模块中删除属性的方法(再次使用原型中的属性)。
// m1.js
export const f = () => "original f"

// __mocks__/m1.js
const originalM1 = require.requireActual("../m1");
// set the original as a prototype
let mockModule: any = Object.create(originalM1);
const __setMock = (name, value) => {
  mockModule[name] = value;
};
const __removeMock = (name) => {
  Reflect.deleteProperty(mockModule, name);
};
// enhance the mocked module to allow overriding original exports
module.exports = Object.assign(mockModule, { __setMock, __removeMock });


// m1.test.js
import { f } from "./m1";

jest.mock("./m1");

test("mocking stuff", () => {
  // here nothing is mocked - the original module is used
  expect(f()).toBe("original f");

  // override the export f of the module
  require("./m1").__setMock("f", () => "mocked f");
  expect(f()).toBe("mocked f");

  // set it back to the original
  require("./m1").__removeMock("f");
  expect(f()).toBe("original f");

  //override with another value
  require("./m1").__setMock("f", () => "another mocked f");
  expect(f()).toBe("another mocked f");
});

我的 2 美分:

我测试了很多解决方案(如果不是全部),唯一对我有用的就是这个(jest 23):

// importedModule.js
export const funcB = () => {
  return "not mocked";
};
export const funcA = () => {
  return mockProxy.funcB(); // this works but it's soooo hacky
};

export const mockProxy = {
  funcB
};

// moduleToTest.js
import { funcA } from "./moduleImported.js";

export default function() {
  return funcA();
}

// test
let moduleImported = require("./moduleImported.js");
moduleImported.mockProxy.funcB = jest.fn(() => "mocked");
const funcToTest = require("./moduleToTest.js").default; // or import

it("should return mocked", function() {
  expect(funcToTest()).toBe("mocked");
});

我得出的结论是,尝试这样做不是一个好主意,因为:

  • test.jsimportedModule.js实现细节了解太多
  • 解决方案是脆弱的,没有人会通过查看importedModule.js来理解mockProxy的用途

有没有人为此找到有效的解决方案?

我正在使用:

"jest": "^21.2.1",
"jest-cli": "^21.2.1",

@jamesone你读过这个https://github.com/facebook/jest/issues/936#issuecomment -410080252 吗?

根据@thomaskempel的回答,这对我

就我而言,我想在 node_modules 中模拟一个依赖项,我们称之为“共享组件”。 它导出许多命名组件。 我只想模拟这些命名导出中的几个,而将其余的作为真实的东西。

所以在__mocks__/shared-components.js我有:

const original = require.requireActual('shared-components');

module.exports = {
...original,
moduleNameToOverride: jest.fn().mockImplementation(() => {
      return 'whatever';
    }),
}

就我而言,我正在剔除实现。 希望这对将来的某人有所帮助。

我最近遇到了同样的问题,这个线程中的对话帮助我更好地理解,我在这里总结了我的发现https://medium.com/@DavideRama/mock -spy-exported-functions-within-a-single- module-in-jest-cdf2b61af642

灵感来自@qwertie的解决方案

mockGet = jest.fn()
jest.mock('my-module', () => ({
  ...jest.requireActual('my-module'),
  get: mockGet
}))

@MajorBreakfastReact.lazy吗?

const mockLazy = jest.fn();

jest.mock('React', () => ({
    ...jest.requireActual('React'),
    lazy: mockLazy
}));

我仍然得到ReferenceError: React is not defined

模拟一个独立的导出功能模块:
从文件中导出默认导出的所有单个功能部分。

例子:
数据道.js

函数 getData()
函数 setData()
函数删除数据()
导出 {getData、setData、deleteData}

现在您可以通过默认命名将文件中的所有函数导入到您的 jest 测试中;

dataDao.spec.js

import * as dataDao from '../dataDao';
// 监视在导入时引用分配的默认名称的模块
jest.spyOn(dataDao, 'getData')
jest.spyOn(dataDao, 'setData')
jest.spyOn(dataDao, 'deleteData')

@vchinthakunta ,这可能有效,但它看起来违反了导出/导入语法的主要目的:其他模块将不再能够通过以下方式导入特定方法或数据字段

import { justThisThing } from 'someModule';

我在那里错过了什么吗?

@MajorBreakfastReact.lazy吗?

const mockLazy = jest.fn();

jest.mock('React', () => ({
    ...jest.requireActual('React'),
    lazy: mockLazy
}));

我仍然得到ReferenceError: React is not defined

我认为“React”在这里应该是小写的,因为它引用了导入?

jest.mock('react'...)

我使用以下方法使我的代码工作,这比我在这里看到的其他解决方案更简单。 它不需要您使用require或设置default导出。

助手/navigation.js

export const initHeader = () => {
    // initialise header
    ...
}

...

export const anotherHelperFunction = () => {
    // do stuff
    ...
}

使用 navigation.js 的组件

import { initHeader } from '../helpers/navigation';

jest.mock('../helpers/navigation');

...

describe('Test component', () => {

    it('should reinitialise header', () => {
        const mockHeaderInit = jest.fn();
        initHeader.mockImplementation(mockHeaderInit);

        const component = mountComponent(mockProps);
        component.simulate('click');

        expect(mockHeaderInit).toBeCalled();
    }
}
mockGet = jest.fn()
jest.mock('my-module', () => ({
  ...jest.requireActual('my-module'),
  get: mockGet
}))
ReferenceError: mockGet is not defined

       4 | const mockGet = jest.fn();
       5 | jest.mock('./browserStorage', () => ({
       6 |   ...jest.requireActual('./browserStorage'),
    >  7 |   get: mockGet,
         |        ^
       8 | }));

jest.mock被吊起,使用doMock

jest.mock('./browserStorage', () => ({
  ...jest.requireActual('./browserStorage'),
  get: jest.fn(),
}));

const {get: mockGet} = require('./browserStorage');

这似乎是一个足够普遍的问题。 jest 文档中有关于此的内容吗?

那么,是否有模拟同一模块中的函数的解决方案?

以下方法对我有用,诀窍是在测试结束时重置模拟函数。
此示例模拟来自 jsonwebtoken 模块的验证功能。

  test('perform my test', async () => {
    // save the real jwt.verify function
    const verify = jwt.verify
    // mock it.
    jwt.verify = jest.fn().mockReturnValue({ sub: 0 })
    // do the test
    ...
    // set the real function back.
    jwt.verify = verify
  })

以下方法对我有用,诀窍是在测试结束时重置模拟函数。
此示例模拟来自 jsonwebtoken 模块的验证功能。

  test('perform my test', async () => {
    // save the real jwt.verify function
    const verify = jwt.verify
    // mock it.
    jwt.verify = jest.fn().mockReturnValue({ sub: 0 })
    // do the test
    ...
    // set the real function back.
    jwt.verify = verify
  })

你在哪里使用 const verify ? 无论如何,这仅在您的模拟函数不是导出的 const 函数时才有效

那么,是否有模拟函数_in同一个模块_的解决方案?

经过大量搜索,此问题的解决方案是将您的导出存储在您的函数和模拟可以引用的单个对象中。 以下文章都达成了相同的共识。

https://github.com/facebook/jest/issues/936#issuecomment -438975674
https://medium.com/@qjli/how -to-mock-specific-module-function-in-jest-715e39a391f4
https://luetkemj.github.io/170421/mocking-modules-in-jest

令我惊讶的是,没有人提到一种不同的解决方案,如果它适用于您的代码,则完全消除所有这些问题并使测试所需的代码非常非常简单:

_将您要模拟的单个函数移动到其自己的模块中。_

严重地。 如果您编写的模块需要将其内部部分与其他内部部分分开测试,那么几乎可以肯定您的类违反了单一职责原则(是的,它不是一个真正的类,但该模块的功能就像一个类,模块是代码的单元容器)。 拆分那个傻瓜,然后繁荣,您可以嘲笑需求。

如果模拟函数依赖于一堆私有状态,这仍然不是不以某种方式拆分模块的好理由。 对我来说,它依赖于一堆内部状态的事实意味着模块的关注点没有被清楚地考虑到。 也许甚至还有第三个模块要拆分,它代表某种数据类或 DTO,可以作为参数传入。

此外,您是否仅出于测试目的导出函数,否则将是私有的? 为什么外部代码会直接调用mocked函数,同时也会调用自己需要调用的其他函数? 我敢打赌这里会发生某种脱节。 也许模拟的函数需要保留,但所有的函数都需要分成两半,其中的一半被移到另一个模块中。 你明白了。

当测试变得非常困难时,几乎总是需要重构的迹象......

不需要测试傻瓜:

const tomfoolery = require('tomfoolery'); // no longer required

阅读此线程并测试建议的解决方案后,我仍然无法使其正常工作。 从我读过的内容来看,有些人完成了这项工作,但我不知道是如何做到的。

有人可以告诉我需要添加到以下示例中以使测试通过的代码吗?

// a.js
export const foo = () => 'foo-' + bar()
export const bar = () => 'bar'
// a.test.js
import {
  foo,
  bar
} from './a'

describe('foo', () => {
  it('should return foo-MOCKED_BAR', () => {
    expect(foo()).toBe('foo-MOCKED_BAR')
  })

  it('should have the mocked implementation of bar', () => {
    expect(bar()).toBe('MOCKED_BAR')
  })
})

describe('bar', () => {
  it('should have the original implementation of bar', () => {
    expect(bar()).toBe('bar')
  })
})

describe('foo and bar together', () => {
  it('should have the original implementation of both', () => {
    expect(foo()).toBe('foo-bar')
  })
})

谢谢!

我只想模拟一个像 lodash.random 这样的 lodash 方法,并且能够很容易地做到:

模块.js

const lodash = require('lodash');

module.exports = function() {
  return lodash.random();
}

测试.js

const lodash = require('lodash');
const module = require('./module.js);

it('mocks lodash', () => {
    jest.spyOn(lodash, 'random').mockImplementationOnce(() => {
      return 2;
    });

    expect(module()).toEqual(2)
});

希望有帮助:)

对我们使用打字稿的团队有用的是创建一个const我们导出而不是直接导出函数。
不工作:
export function doSomething(a, b) {}
在职的:
export const doSomething = function (a, b) {}

我像@arbielsk所做的那样更改了导出,它有效! 但是不知道两种出口有什么区别...

@dgrcode你有没有找到你的例子的解决方案? 据我所知,通过 Jest 进行模拟不支持您尝试做的事情。 具体来说,我认为模拟只是重新连接导入,以便模块的外部视图看到模拟的方法。 但是,在您的示例中, foobar位于同一模块中,因此无法模拟foobar的看法。

我相信您的选择是:
1) 重新组织您的代码,以便foo导入包含bar
2) 使用babel-plugin-rewire

如果我理解有误,请纠正我!

我有一个稍微不同的要求:我想为一个函数模拟整个模块 _except_。 使用@MajorBreakfast的解决方案作为起点,我想出了以下内容:

jest.mock('my-module', () => ({
  ...jest.genMockFromModule('my-module'),
  myFunction: jest.requireActual('my-module').myFunction
}))

@dgrcode你有没有找到你的例子的解决方案? 据我所知,通过 Jest 进行模拟不支持您尝试做的事情。 具体来说,我_认为_只是模拟基本上是重新连接导入,以便模块的外部视图看到模拟的方法。 但是,在您的示例中, foobar位于同一模块中,因此无法模拟foobar的看法。

我相信您的选择是:

  1. 重新组织您的代码,以便foo导入包含bar
  2. 使用babel-plugin-rewire

如果我理解有误,请纠正我!

那实际上是我的情况
我对如何模拟模块的理解一团糟🤦‍♂

基本上
如果 2 个函数在同一个模块中,并且相互调用

如果foo正在调用bar

function bar() {
  return 'some-result'
}

function foo(){
  return bar()  // <-- the function I want to mock 
}

bar放在一个新文件中(被嘲笑)

我在一个新文件中移动了bar方法,现在我可以使用上面的许多示例
非常感谢@yoni-abtech 让我明白了🤣

在阅读了整篇文章并一遍又一遍地测试后,我看到它的方式有 3 个选项......

选项 1 - 使用const声明所有函数

这要求您强制使用函数表达式而不是声明。 幸运的是, func-style eslint 规则得到了你的支持。

使用export const允许您使用spyOn函数,这些函数被_same_模块中的其他函数使用。

// hello.js
export const message = () => {
  return 'Hello world';
}

export const foo = () => {
  return message();
}
// hello.test.js
import * as testModule from './hello.js';

describe('test spyon with function expressions', function () {
  afterAll(() => {
    jest.restoreAllMocks();
  });
  it('should NOT mock message in foo', function () {
    const actual = testModule.foo();

    expect(actual).toBe('Hello world');
  });

  it('should mock message in foo', function () {
    jest.spyOn(testModule, 'message').mockReturnValue('my message');

    const actual = testModule.foo();

    expect(actual).toBe('my message');
    expect(testModule.message).toHaveBeenCalledTimes(1);
  });
});

选项 2 - 使用rewire babel 插件

如果您不想强制使用函数表达式(即使用const ),这可能是一个好方法。

这允许您_rewire_ (又名模拟)来自同一模块的函数。 我可以想象代码看起来像下面那样,但还没有测试过。 同样从他们的文档来看,您似乎可以在同一模块中重新连接甚至未从模块导出的函数👍,想象一下下面的示例,而无需导出message函数。

例子:

// hello.js
export function message() {
  return 'Hello world';
}

export function foo() {
  return message();
}
// hello.test.js
import * as testModule from './hello.js';

describe('test rewire api', function() {
  it('should NOT mock message in foo', function () {
    const actual = testModule.foo();

    expect(actual).toBe('Hello world');
  });

  it('should mock message in foo', function () {
    testModule.__RewireAPI__.__Rewire__('message', jest.fn().mockReturnValue('my message'));

    const actual = testModule.foo();

    expect(actual).toBe('my message');
    expect(testModule.message).toHaveBeenCalledTimes(1);
    testModule.__RewireAPI__.__ResetDependency__('message');
  });
});

有关示例,请参阅文档

注意:需要 babel 转译

选项 3 - 将所有功能分离到单独的模块/文件中

此选项最不受欢迎,但显然可以与典型的mock功能配合使用。


PS:虽然这个步骤非常有启发性并且经常很有趣,但我希望这个概要可以减轻其他人阅读整个线程的需要。 ✌️

谢谢@nickofthyme ,你刚刚结束了几天的抨击。

@nickofthyme你的option 1在我的应用程序和create react app中都失败

 FAIL  src/hello.test.js
  test spyon with function expressions
    ✓ should NOT mock message in foo (3ms)
    ✕ should mock message in foo (6ms)

  ● test spyon with function expressions › should mock message in foo

    expect(received).toBe(expected) // Object.is equality

    Expected: "my message"
    Received: "Hello world"

      17 |     const actual = testModule.foo();
      18 |
    > 19 |     expect(actual).toBe("my message");
         |                    ^
      20 |     expect(testModule.message).toHaveBeenCalledTimes(1);
      21 |   });
      22 | });

      at Object.toBe (src/hello.test.js:19:20)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        1.848s
Ran all test suites related to changed files.

@danielhusar你似乎是对的。 抱歉,我应该用 CRA 对此进行测试。

我得到了选项 1 中描述的模拟在这里工作。 使用yarn test:hello脚本对其进行测试。

结果

> yarn test:hello
yarn run v1.16.0
$ jest --config=jest.config.js -t=hello --verbose
 PASS  src/hello/hello.test.ts
  test hello
    ✓ should NOT mock message in foo (3ms)
    ✓ should mock message in foo (1ms)

Test Suites: 1 skipped, 1 passed, 1 of 2 total
Tests:       1 skipped, 2 passed, 3 total
Snapshots:   0 total
Time:        1.392s
Ran all test suites with tests matching "hello".
✨  Done in 2.36s.

它需要使用自定义jest.config.js文件使用ts-jest并直接调用jest --config=./jest.config.js ,而不是通过react-scripts 。 我不确定react-scripts是如何配置 jest 的,但我认为可能有一种方法可以以某种方式更新配置。

此修复删除了*.css*.svg文件的转换,因此请忽略App.tsx错误。

有没有什么特别需要做的事情才能让它发挥作用?
我会说我有非常标准的设置(没有 ts)并且它不能开箱即用。

今晚我会再看一下,看看是否可能。

@danielhusar我昨晚查看了​​,无法获得开箱即用的解决方案。 关键是笑话配置transformer ,CRA允许您在package.json#jest覆盖它。 js 和 ts 文件使用babel-jestreact-scripts阻止您使用.babelrc配置文件并设置它们在react-scripts test设置的test env react-scripts test在这里

我希望我能深入挖掘,但现在没有时间。

嗯,我仍然有点努力让它工作(在我的自定义设置上,而不是 cra)。
(最新版本的 jest 和 babel-jest)

这是我的笑话配置:

module.exports = {
  name: 'my-app',
  testURL: 'http://localhost/',
  setupFiles: ['<rootDir>/setup-jest.js'],
  setupFilesAfterEnv: ['<rootDir>/setup-test.js'],
  testMatch: ['**/__tests__/**/*.test.js?(x)', '**/?(*.)+(spec|test).js?(x)'],
  testEnvironment: 'jest-environment-jsdom-fifteen',
  snapshotSerializers: ['enzyme-to-json/serializer'],
  globals: {
    ENV: 'test',
  },
  transform: {
    '^.+\\.[t|j]sx?$': 'babel-jest',
  },
};

还有我的 babelrc:

{
  "presets": [
    "@babel/react",
    ["@babel/env", {
      "corejs": "3",
      "useBuiltIns": "entry",
      "loose": true,
      "exclude": [
        "es.string.split"
      ]
    }],
    "@babel/flow"
  ],
  "plugins": [
    "array-includes",
    "lodash",
    "@babel/plugin-syntax-dynamic-import",
    "@babel/plugin-syntax-class-properties",
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-proposal-object-rest-spread",
    "@babel/plugin-proposal-optional-chaining"
  ],
  "env": {
    "test": {
      "plugins": ["dynamic-import-node"]
    }
  }
}

对于任何在选项 1 中挣扎的人来说,使用() => { return expression }而不是() => (expression)函数很重要。

我将选项 1 修改为:

import * as test from './test';

export const message = () => {
    return 'Hello world';
  }

  export const foo = () => {
    return test.message();
  }

不漂亮,但它应该工作。

@nickofthyme ,您拥有的选项 1是正确的。 但是如果你把代码改成:

const foo = () => {}
export { foo }

然后它坏了。 大概是因为您创建了一个新的对象文字并将其导出。

有趣的观察。 谢谢@maletor

Jest 在他们的文档中提供了一个非常简单明了的示例,说明了如何部分模拟模块。 这适用于 ES import 和 Node require 语句。
https://jestjs.io/docs/en/jest-object#jestrequireactualmodulename

@johncmunson这是一个很好的观点。 但是,您展示的这个模拟模块的示例仅在您只需要运行jest.mock _once_ 并且没有任何模拟方法使用该模块的另一个导出时才有效。

以上面的例子为例......我添加了bar来展示我想如何在foobar之间foo不同的方式模拟模块。

export const message = (): string => {
  return 'Hello world';
}

export const foo = (): string => {
  return message();
}

export const bar = (): (() => string) => {
  return foo;
}

jest.mockjest.requireActual我认为会是这样。

import * as mockTestModule from './hello';

jest.mock('./hello');
const actualTestModule = jest.requireActual('./hello');

describe('test hello', function () {
  afterAll(() => {
    jest.restoreAllMocks();
  });

  // first test doesn't depend on any other method of the module so no mocks
  it('should NOT mock message in foo', function () {
    const actual = actualTestModule.foo();

    expect(actual).toBe('Hello world');
  });

  // the second I want to mock message in foo
  it('should mock message in foo', function () {
    jest.spyOn(mockTestModule, 'message').mockReturnValue('my message');
    const actual = actualTestModule.foo();

    expect(actual).toBe('my message'); // fails
    expect(mockTestModule.message).toHaveBeenCalledTimes(1); // never called
  });

  it('should mock foo in bar', function () {
    jest.spyOn(mockTestModule, 'foo').mockReturnValue('my message');
    const actual = actualTestModule.bar();

    expect(actual()).toBe('my message'); // fails
    expect(mockTestModule.message).toHaveBeenCalledTimes(1); // never called
  });
});

我什至尝试用jest.doMock分别模拟它们,但仍然得到相同的结果。


点击查看代码

```ts
import * as testModule from './hello';

描述('测试你好',函数(){
afterAll(() => {
jest.restoreAllMocks();
});

it('should NOT mock message in foo', function () {
  const actual = testModule.foo();

  expect(actual).toBe('Hello world');
});

it('should mock message in foo', function () {
  jest.doMock('./hello', () => {
    // Require the original module to not be mocked...
    const originalModule = jest.requireActual('./hello');

    return {
      ...originalModule,
      message: jest.fn().mockReturnValue('my message'),
    };
  });
  const actual = testModule.foo();

  expect(actual).toBe('my message'); // fails
  expect(testModule.message).toHaveBeenCalledTimes(1); // never called
});

it('should mock foo in bar', function () {
  jest.doMock('./hello', () => {
    // Require the original module to not be mocked...
    const originalModule = jest.requireActual('./hello');

    return {
      ...originalModule,
      foo: jest.fn().mockReturnValue('my message'),
    };
  });
  const actual = testModule.bar()();

  expect(actual).toBe('my message'); // fails
  expect(testModule.foo).toHaveBeenCalledTimes(1); // never called
});

});
``

这种方法的问题是需要实际模块,然后说调用foo ,仍然调用实际的message函数而不是模拟。

我希望它如此简单,但从我所见,这对本线程中的示例没有帮助。 如果我在这里遗漏了什么,请告诉我。 我会很乐意承认错误。

对于遇到此问题寻找解决方案的任何人,在一个文件中导出许多常量/函数并将它们导入到我正在测试的文件中时,以下内容似乎对我有用

function mockFunctions() {
  const original = require.requireActual('../myModule');
  return {
    ...original, //Pass down all the exported objects
    test: jest.fn(() => {console.log('I didnt call the original')}),
    someFnIWantToCurry: {console.log('I will curry the original') return jest.fn((...args) => original.someFnIWantToCurry(...args)}),
  }
jest.mock('../myModule', () => mockFunctions());
const storage = require.requireMock('../myModule');
`

以下工作并且有点短:

const module = require('./module');
jest.spyOn(module, 'myFn').mockImplementation(() => 'val');

在打字稿中,只需import而不是require

import * as module from './module';

这具有使生活易于恢复原始功能和清除模拟的优点。

对于遇到此问题寻找解决方案的任何人,在一个文件中导出许多常量/函数并将它们导入到我正在测试的文件中时,以下内容似乎对我有用

function mockFunctions() {
  const original = require.requireActual('../myModule');
  return {
    ...original, //Pass down all the exported objects
    test: jest.fn(() => {console.log('I didnt call the original')}),
    someFnIWantToCurry: {console.log('I will curry the original') return jest.fn((...args) => original.someFnIWantToCurry(...args)}),
  }
jest.mock('../myModule', () => mockFunctions());
const storage = require.requireMock('../myModule');
`

以下工作并且有点短:

const module = require('./module');
jest.spyOn(module, 'myFn').mockImplementation(() => 'val');

在打字稿中,只需import而不是require

import * as module from './module';

这具有使生活易于恢复原始功能和清除模拟的优点。

哦,是的,如果您的对象仅定义了getter则此方法也不起作用。 错误消息可能如下所示:

Test suite failed to run

    TypeError: Cannot set property useContent of #<Object> which has only a getter

对于这种情况,可能需要使用jest.mock(..) 。 :bowing_man:

我的模拟使用以下方法工作:

import { unsubscribe } from "../lib/my-lib"
import { MyComponent } from "./index"

test("unsubscribe gets called", () => {
    const module = require("../lib/my-lib")
    jest.spyOn(
        module,
        "unsubscribe"
    ).mockImplementation(() => jest.fn())

    const { getByTestId } = render(() => <MyComponent  />)

    let button = getByTestId("trigger.some.action.button")

    fireEvent.press(button)

    expect(unsubscribe).toHaveBeenCalled()
})

它看起来不是那么优雅,也不是那么容易扩展,但它工作得很好并且适合我现在的情况..但是如果有人可以提出任何其他很棒的语法! 这是唯一似乎有效的语法。

es6模块代码:

export const funcA = () => {};
export const funcB = () => {
  funcA();
};

转译为 CommonJS 后:

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.funcB = exports.funcA = void 0;

var funcA = function funcA() {};

exports.funcA = funcA; // You can mock or add a spy  on this `funcA`

var funcB = function funcB() {
  funcA();  // This is still original `funcA`
};

exports.funcB = funcB;

有很多方法可以解决这种情况。

  1. 您需要像这样更改代码,以便您可以使用模拟/监视的funcA
function funcA() {}
exports.funcA = funcA;

function funcB() {
  exports.funcA(); // Now, this `exports.funcA` is added a spy or mocked. Keep the same reference to `funcA`
}
exports.funcB = funcB;

或者,

export let funcA = () => {};
export const funcB = () => {
  exports.funcA();
};

单元测试结果:

 PASS  myModule.test.ts (9.225s)
  funcB
    ✓ should call funcA (3ms)

-------------|---------|----------|---------|---------|-------------------
File         | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------|---------|----------|---------|---------|-------------------
All files    |     100 |      100 |     100 |     100 |                   
 myModule.ts |     100 |      100 |     100 |     100 |                   
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        10.967s
  1. 使用rewire包模拟funcA
    ...

此外,你需要看看这个文档: https : require到底做

这个stackoverflow帖子中的解决方案对我有用
https://stackoverflow.com/a/53402206/1217998

基本上首先您将所有要转换的函数转换为jest.fn

jest.mock('../../utils', () => {
  const actualUtils = jest.requireActual('../../utils');
  const originalImplementation = actualUtils.someFun;

  return {
    ...actualUtils,
    someFun: jest.fn()
      .mockImplementation(originalImplementation),
  };
});
const utils = require('../../utils');

然后你可以像往常一样使用它,如果你想或者像这样嘲笑它

jest.spyOn(utils, 'someFun').mockReturnValueOnce(true);

您可以使用它来获取原始实现

beforeEach(() => {
    jest.clearAllMocks();
  });

这个stackoverflow帖子中的解决方案对我有用
https://stackoverflow.com/a/53402206/1217998

基本上首先您将所有要转换的函数转换为jest.fn

jest.mock('../../utils', () => {
  const actualUtils = jest.requireActual('../../utils');
  const originalImplementation = actualUtils.someFun;

  return {
    ...actualUtils,
    someFun: jest.fn()
      .mockImplementation(originalImplementation),
  };
});
const utils = require('../../utils');

然后你可以像往常一样使用它,如果你想或者像这样嘲笑它

jest.spyOn(utils, 'someFun').mockReturnValueOnce(true);

您可以使用它来获取原始实现

beforeEach(() => {
    jest.clearAllMocks();
  });

谢谢!

Jest 在他们的文档中提供了一个非常简单明了的示例,说明了如何部分模拟模块。 这适用于 ES import 和 Node require 语句。
https://jestjs.io/docs/en/jest-object#jestrequireactualmodulename

从模块内调用模拟函数时不起作用。

此外,我发现有时以不更改原始函数而是使用一些自定义(附加)变量调用函数的方式模拟函数会很有用:

jest.mock('./someModule', () => {
  const moduleMock = require.requireActual('./someModule');
  return {
    ...moduleMock,
    // will mock this function 
    someFunction: (args) =>
      moduleMock.someFunction({
        ...args,
        customArgument,
      }),
  };
});

在我的情况下,我需要将配置传递给函数,否则它将使用默认配置。

这是我发现的唯一方法,所以如果你想出一些更好或更简单的想法,会很高兴听到:)

FWIW 我在https://github.com/magicmark/jest-how-do-i-mock-x/blob/master/src/function-in-same-module/README中将各种方法与可运行的示例结合在一起

这不回答 OP 问题/问题,而是涉及一些重构的解决方案。 我发现将我的函数分成不同的文件,然后模拟这些导入是最简单的事情。

// package.json
...
"scripts": {
    "test": "jest",

...
"devDependencies": {
    "@babel/preset-env": "^7.11.5",
    "jest": "^24.9.0",
...

```js
// babel.config.js

模块.出口 = {
预设:[
[
'@babel/preset-env',
{
目标:{
节点:'当前',
},
},
],
],
};

```js
// module-utils.js

export const isBabaYaga = () => {
  return false
}

// module.js

import { isBabaYaga } from './module-utils'

export const isJohnWickBabaYaga = () => {
  return isBabaYaga()
}
// modules.test.js

import { isJohnWickBabaYaga } from './module';

jest.mock('./module-utils', () => ({
    isBabaYaga: jest.fn(() => true)
}))

test('John Wick is the Baba Yaga', () => {

    // when
    const isBabaYaga = isJohnWickBabaYaga()

    // then
    expect(isBabaYaga).toBeTruthy()
});
PASS  src/module.test.js
✓ John Wick is the Baba Yaga (4ms)

我最近遇到了这个问题。 建议的解决方案都不适用于我,因为我无法更改代码。 babel-plugin-rewire 也对我不起作用。 有没有其他解决方案来测试一个函数是否被同一模块中的另一个函数调用? 老实说,这似乎应该可以工作,或者应该有一个 babel 插件可以做到这一点。 任何帮助将非常感激!

我最近遇到了这个问题。 建议的解决方案都不适用于我,因为我无法更改代码。 babel-plugin-rewire 也对我不起作用。 有没有其他解决方案来测试一个函数是否被同一模块中的另一个函数调用? 老实说,这似乎应该可以工作,或者应该有一个 babel 插件可以做到这一点。 任何帮助将非常感激!

您是否查看过https://github.com/facebook/jest/issues/936#issuecomment -659597840? 那里有一个最小的复制品,可以模拟同一文件中的函数调用。

此页面是否有帮助?
0 / 5 - 0 等级