<p>jest.mock factory 在测试中不起作用</p>

创建于 2017-01-12  ·  38评论  ·  资料来源: facebook/jest

您要请求功能还是报告错误
漏洞

目前的行为是什么?

似乎用工厂创建模拟的方法在testit不起作用。 只有在文件的根级别定义了模拟时,它才起作用。

这是我的模拟示例:

jest.mock('services/feature', () => ({
    isEnabled: () => true
}));

什么是预期行为?

在测试中模拟文件应该可以工作。

请提供您确切的 Jest 配置并提及您的 Jest、节点、yarn/npm 版本和操作系统。

玩笑 18.0.0、节点 7.4、macOS

Confirmed Discussion

最有用的评论

要在测试之间更改模拟的返回值,您可以执行以下操作:

jest.mock('whatever');

// Get the mock function
const whatever = require('whatever');

test('test 1', () => {
  whatever.mockImplementation(() => 'hello');
});

test('test 2', () => {
  whatever.mockImplementation(() => 'world');
});

所有38条评论

jest.mock调用会通过 babel-jest 转换自动提升到文件顶部。 您可以使用jest.doMock省略此行为。 你试过吗?

doMock

在文档中,我可以阅读

注意:使用babel-jest ,对 mock 的调用将自动提升到代码块的顶部。 如果您想明确避免这种行为,请使用doMock

但是...测试就是一个代码块,对吧? 所以就我而言,我不希望看到任何差异。

这是一个完整的测试

it('renders with the enabled feature', () => {
  jest.mock('services/feature', () => ({
      isEnabled: () => true
  }));

  const component = renderer.create(
      <MyComponent />
  );

  const tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});

您能否在 GH 存储库中提供此副本?

当然。 这是: https :

两个测试都呈现“已禁用”,即使其中一个测试有一个模拟来呈现“已启用”。
看到这些:
https://github.com/tleunen/jest-issue-2582/blob/master/src/MyComponent.js
https://github.com/tleunen/jest-issue-2582/blob/master/src/__tests__/MyComponent.spec.js

谢谢。

对这个问题有什么建议@thymikee @cpojer
我在同一个文件中有几个测试,我想对每个测试都有不同的模拟响应。

很高兴我发现了这个问题,为什么jest.mock()在我的describe范围内不起作用,我很困惑。 将它移到顶部(在测试文件中我的导入下方)并且它可以工作。

对我来说,它也适用于没有工厂的jest.mock() ,使用包含模拟文件的__mocks__文件夹。

- 编辑

显然有必要将jest.mock()语句提升到 import 语句之前。 但是@tleunen ,这可能意味着不可能多次模拟同一个文件,并给出不同的响应。 也许最好让工厂函数更具动态性,这样它就可以在每个测试用例中为您提供不同的结果。

在我看来,如果jest.mock()总是放在describeit块之外,它会更明确。 但这应该在文档中明确说明

所以jest.mock被提升到函数作用域,这就是为什么它不适用于require s(绝对不是import s 被提升到模块作用域),如果你在describe以外的函数中调用它(Jasmine 对其进行了特殊处理)。
您的jest.mock调用将被提升到该函数(而不是模块)的顶部,这就是它无法按您期望的方式工作的原因。

通常,如果您希望它们在测试用例中不同,我们建议在beforeEachafterEach设置不同的模拟。

@cpojer可以详细说明这一点,如果我们想提升对上层作用域的调用。

这是因为您在模块初始化时需要您的模块(使用导入)。 jest.mock稍后被调用。 解决这个问题的方法是:

beforeEach(() => { // or the specific test
  jest.mock('MyModule', () => …);
  const MyModule = require('MyModule');
  …
});

等等。

如果您要在 beforeEach 中执行此操作,我不清楚您将如何区分测试(那么您将如何为每个测试提供不同的模拟?)

将它放入测试本身当然有效。

然后你可以在测试组件中传递模拟模块吗?

如果我不导入/需要我想要模拟的文件(例如我正在导入的其他文件的依赖项)但我希望它的范围限定为 describe/it 块怎么办? 或者即使我想对 beforeEach/beforeAll 测试进行不同的模拟? 这些情况可能吗?

// A.js depends on B.js
import A from './A';

describe('myTest', () => {

    describe('myFirstScope', () => {
        beforeAll(() => {
            jest.mock('./B', () => ({
                myFirstMethod: jest.fn(),
            }));
        });

        // tests here
    });

    describe('mySecondScope', () => {
        beforeAll(() => {
            jest.mock('./B', () => ({
                mySecondMethod: jest.fn(),
            }));
        });

        // tests here
    });
});

在这种情况下,您需要在模拟 B 之后要求 A。(不使用import ,而是require )。

模拟 B 后需要 A

对我不起作用,仍然看到 B 的原始模拟传递到结果

@GoldAnna您找到解决此问题的方法了吗?

@alayor我没有

尽管我遇到过这个问题很多次,但我现在确信我要么没有正确测试,要么没有按照作者的意图使用jest ,或者两者兼而有之。 进一步的证据是,在jest的文档中几乎没有一个模拟相关的例子对我来说似乎是真实世界的例子......它们可能是,我的方法可能是不正确的。

也就是说,以下对我有用,并且以下所有测试都通过了。 请注意,要返回ModuleB的原始版本,我必须同时调用jest.resetModules()jest.unmock('./moduleB') ...这些顺序无关紧要。

// Module A
const ModuleB = require('./moduleB');
const ModuleA = function() {
  this.title = new ModuleB().title;
  return this;
};
module.exports = ModuleA;

// Module B
const ModuleB = function() {
  this.title = 'Module B - Original'
  return this;
};
module.exports = ModuleB;

// Tests
describe('Jest a few tests', () => {
  it('should do something', () => {
    jest.resetModules();
    jest.mock('./moduleB', () => function() {
      this.title = 'Module B - Mock 1'
      return this;
    });
    const ModuleA = require('./moduleA');
    const moduleA = new ModuleA();
    expect(moduleA.title).toEqual('Module B - Mock 1');
  });

  it('should do something else', () => {
    jest.resetModules();
    jest.mock('./moduleB', () => function() {
      this.title = 'Module B - Mock 2'
      return this;
    });
    const ModuleA = require('./moduleA');
    const moduleA = new ModuleA();
    expect(moduleA.title).toEqual('Module B - Mock 2');
  });

  it('should do something original', () => {
    jest.resetModules();
    jest.unmock('./moduleB');
    const ModuleA = require('./moduleA');
    const moduleA = new ModuleA();
    expect(moduleA.title).toEqual('Module B - Original');
  });
});

嘿,

这里没有什么对我有用。
有人可以解释为什么不能在其中使用 jest.mock 吗?

谢谢

我认为这仍然是一个应该作为功能请求重新打开的问题。 我想单独编写测试。 将东西转储到 mocks 文件夹或使用 beforeEach 重新布线通常最终会得到一个垃圾抽屉/奇怪的数据实例化,这些数据实例化会被拖到每个测试中。 我想写一个小模拟并通过一个小测试。

我们没有隔离单个测试的真正方法(考虑test.concurrent )。 如果我们采用类似于 tap 或 ava 的 API,那是有可能的,但我认为在当前全局 API 强加的约束下这是不可能的。 超级高兴被证明是错误的!

在这里尝试使用多种方法来重构以前用 mocha 和 proxyquire 编写的测试,最终将测试分成不同的文件用于不同的模拟。

@gcox您是否尝试过在模块的顶层进行模拟(并在之后要求 moduleA),然后为每个需要不同行为的测试更改模拟的实现? 您可以使用mockImplementation来执行此操作。 这就是我们在测试套件中解决这个问题的方法。

@antgonzales我认为如果模块导入被提升到模块的顶部,这很难做到。 在 node.js 中修改先前导入的模块的引用似乎不是微不足道的,甚至是不可能的。

@jkomusin绝对。 IMO,这不是 OP 所要求的。 我相信他们在问,特别是“我如何强制require('whatever')跨多个相邻测试而不是同一个对象返回不同的模拟?”。

要在测试之间更改模拟的返回值,您可以执行以下操作:

jest.mock('whatever');

// Get the mock function
const whatever = require('whatever');

test('test 1', () => {
  whatever.mockImplementation(() => 'hello');
});

test('test 2', () => {
  whatever.mockImplementation(() => 'world');
});

@gcox 的回答对我有用,但有用,而且简单得多!

@rafaeleyng但如果您从模块导出的不是函数, SimenB 将不起作用...

@gcox示例包含我过去几个小时一直在文档中搜索的大部分信息。 我建议它应该包含在官方文档中,无论如何,目前它非常稀少。

@舒曼德是对的。 请让官方文档更加清晰明了。

PR 总是欢迎改进文档 🙂

模块示例基于
https://github.com/facebook/jest/issues/2582#issuecomment -378677440 ❤️

嘿,通过链接的问题找到了这个,我想通了:

jest.mock('child_process')
const childProcess = require('child_process')

describe('foo', () => {
  test('bar', () => {
    childProcess.execSync.mockImplentation(jest.fn().mockReturnValueOnce('wibble'))
    // code that itself requires child_process
    expect(childProcess.execSync.mock.results[0]).toEqual('wibble')
  })

  test('baz', () => {
    childProcess.execSync.mockImplentation(jest.fn().mockReturnValueOnce('wobble'))
    // code that itself requires child_process
    expect(childProcess.execSync.mock.results[0]).toEqual('wobble')
  })
})

如果您需要在所需代码上模拟多个函数/方法,则需要单独设置它们,而不是在全局执行时使用工厂。

如果你喜欢我只需要一个函数,你可以简化整个事情:

jest.mock('child_process')
const { execSync } = require('child_process')

describe('foo', () => {
  test('bar', () => {
    execSync.mockImplentation(jest.fn().mockReturnValueOnce('wibble'))
    // code that itself requires child_process
    expect(execSync.mock.results[0]).toEqual('wibble')
  })

  test('baz', () => {
    execSync.mockImplentation(jest.fn().mockReturnValueOnce('wobble'))
    // code that itself requires child_process
    expect(execSync.mock.results[0]).toEqual('wobble')
  })
})

如何模拟 json 文件或映射常量等非函数依赖项。 请让文档更清楚如何处理这个......

@gcox您的解决方案是我找到的唯一一个解决方案,当测试中的导入模块导入另一个我在__mocks__文件夹中手动模拟的模块时。

例如,测试文件调用jest.mock('./ModuleA') ,它在__mocks__/ModuleA.js有一个模拟。 但是 ModuleA 不在测试中,ModuleB - 需要 ModuleA - 正在测试中。 如果没有您的解决方案,ModuleB 将获得 ModuleA 的实际实现,而不是模拟。

这似乎是开玩笑的奇怪行为。 我希望当我在模块上调用 jest.mock 时,任何依赖于模拟模块的被测模块都将使用模拟。 这看起来很奇怪,它不能那样工作,还是我完全错误地处理这个问题?

@SimenB在您的示例中,您需要模拟模块,并在测试中执行它,这确实有效。 您是否希望我上面描述的场景也能正常工作? 非常感谢。

@SimenB为我提供了更简单的解决方案。 谢谢! 在我花时间用许多不同的方式之前,Wish 可以更早地找到它

在我的情况下,我正在模拟并将错误的路径导入到我想要存根的模块中。 我在文件路径中有不同的大小写,所以我的编辑器认为没问题,但只是失败了。

曾是

const { stubMethod } = require("./path/to/File");
jest.mock("./path/to/File");

更改为(将File大小写更改为file ):

const { stubMethod } = require("./path/to/file");
jest.mock("./path/to/file");

希望这对其他人有帮助。

Jasmine spyOn() _inside_ 测试没有这样的问题。 Jest 据说默认使用茉莉花? 为什么它在测试中不起作用?

茉莉花示例:

spyOn(require('moduleB'), 'functionA').and.callFake(() => true);

当谈到模拟模块时,我不太明白的是,它总是被描述为好像您想主要在测试功能中使用该模块。 例如,如果您查看文档中doMock函数的示例代码:

test('moduleName 2', () => {
  jest.doMock('../moduleName', () => {
    return {
      __esModule: true,
      default: 'default2',
      foo: 'foo2',
    };
  });
  return import('../moduleName').then(moduleName => {
    expect(moduleName.default).toEqual('default2');
    expect(moduleName.foo).toEqual('foo2');
  });
});

在此示例中,您只能在测试函数中访问"moduleName"的新模拟版本。 对我来说,更重要的是我的“非测试”代码也可以访问模块的模拟版本。 例如,我在测试中使用的类,导入了"moduleName"仍将使用原始版本,而不是模拟版本。

似乎是相关的: https :

提示 - 如果您有一个导出原始值的模块,例如:

```auth.js
export const isUserAdmin = getSetting('admin');

And you want to use a mock value instead, in the test, then a simple require and assignment seems to do the trick:

```deleteUser.js
const auth = require('../auth');

describe('deleteUser', () => {
  it('can delete if admin', () => {
    auth.isUserAdmin = true;

    // call method that depends on isUserAdmin value
    // and assert on the result
  });
});

所以基本上,如果你想模拟一个被你测试的代码间接使用的对象, jest.doMock()函数将不起作用:

import myModuleToTest from './myModuleTotest'

describe('Given my module', () => {
  it('property1 will work as expect', () => {
    // Testing parts of the module that don't need to be mocked
  })

  it('property2 will work as expected', () => {
    jest.doMock('./myOtherModule', () => {
      return {
        __esModule: true,
        default: 'default2',
        foo: 'foo2',
      };
    });

    import('./myOtherModule').then(myOtherModule => {
      // I'm not interested on the mocked module myOtherModule but on the module that makes use of it
      myModuleToTest.doSomethingToSomeProperty(); // At this point myOtherModule's original module and not its mocked version will be used by myModuleToTest
      expect(myModuleToTest.someProperty).toBe('thisWillFail'); // The test won't pass because the mocked version wasn't used
    });
  });
});

从 Jest 26 开始,没有办法多次模拟导出Object的模块被间接使用(我的意思是模拟Function以外的其他东西,因为没有mockFn.mockImplementation(fn)用于模拟Objects )。 这是正确的还是我遗漏了什么? 唯一的解决方案是使用多个测试文件来测试同一个模块。

您必须在模拟之后导入 myModuleToTest 因为如果它导入
在模拟之前没有任何意义。 所以,不要使用 import .... on top 或
在回调内部,因为它无论如何都被提升了。

2020 年 7 月 7 日,星期二,晚上 10:24 Antonio Redondo通知@ github.com
写道:

所以基本上,如果你想模拟一个被间接使用的对象
您测试 jest.doMock() 函数的代码将不起作用:

从 './myModuleTotest' 导入 myModuleToTest
它('会工作',()=> {
jest.doMock('./myOtherModule', () => {
返回 {
__esModule: 真,
默认值:'default2',
foo: 'foo2',
};
});

return import('../myOtherModule').then(myOtherModule => {
// 我对模拟模块 myOtherModule 不感兴趣,但对使用它的模块感兴趣
myModuleToTest.doSomethingToSomeProperty(); // 此时 myOtherModule 的原始模块而不是它的模拟版本将被 myModuleToTest 使用
期望(myModuleToTest.someProperty).toBe('thisWillFail'); // 测试不会通过,因为没有使用模拟版本
});});

从 Jest 26 开始,无法模拟导出对象的模块(我
指间接使用的函数以外的其他东西。 这是
正确还是我遗漏了什么? 唯一的解决办法就是拥有更多
不止一个测试文件来测试同一个模块。


您收到此消息是因为您发表了评论。
直接回复本邮件,在GitHub上查看
https://github.com/facebook/jest/issues/2582#issuecomment-655110424 ,或
退订
https://github.com/notifications/unsubscribe-auth/AAJR3UW6HARW44ZLKAUB7PLR2N77NANCNFSM4C4I7QSQ
.

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