Javascript 如何在单个测试的基础上更改模拟实现 [Jestjs]

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/48790927/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 04:15:05  来源:igfitidea点击:

How to change mock implementation on a per single test basis [Jestjs]

javascriptunit-testingmockingjestjs

提问by Andrea Carraro

I'd like to change the implementation of a mocked dependencyon a per single test basisby extending the default mock's behaviour and reverting it backto the original implementation when the next test executes.

我想通过扩展默认 mock的行为并在下一个测试执行时将其恢复到原始实现来更改基于每个测试的模拟依赖项的实现。

More briefly this is what I'm trying to achieve:

更简单地说,这就是我想要实现的目标:

  1. mockdependency
  2. change/extend mock implementationin a single test
  3. revert backto original mock when next test executes
  1. 模拟依赖
  2. 在单个测试中更改/扩展模拟实现
  3. 下一个测试执行时恢复到原始模拟

I'm currently using Jest v21.

我目前正在使用Jest v21.

Here is what a typical Jest test would look like:

这是典型的 Jest 测试的样子:

__mocks__/myModule.js

__mocks__/myModule.js

const myMockedModule = jest.genMockFromModule('../myModule');

myMockedModule.a = jest.fn(() => true);
myMockedModule.b = jest.fn(() => true);

export default myMockedModule;

__tests__/myTest.js

__tests__/myTest.js

import myMockedModule from '../myModule';

// Mock myModule
jest.mock('../myModule');

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

describe('MyTest', () => {
  it('should test with default mock', () => {
    myMockedModule.a(); // === true
    myMockedModule.b(); // === true
  });

  it('should override myMockedModule.b mock result (and leave the other methods untouched)', () => {
    // Extend change mock
    myMockedModule.a(); // === true
    myMockedModule.b(); // === 'overridden'
    // Restore mock to original implementation with no side effects
  });

  it('should revert back to default myMockedModule mock', () => {
    myMockedModule.a(); // === true
    myMockedModule.b(); // === true
  });
});

Here is what I've tried so far:

这是我迄今为止尝试过的:



1 - mockFn.mockImplementationOnce(fn)

1 - mockFn.mockImplementationOnce(fn)

pros

优点

  • Reverts back to original implementation after first call
  • 第一次调用后恢复到原始实现

cons

缺点

  • It breaks if the test calls bmultiple times
  • It doesn't revert to original implementation until bis not called (leaking out in the next test)
  • 如果测试b多次调用它会中断
  • 它不会恢复到原始实现,直到b不被调用(在下一个测试中泄漏)

code:

代码:

it('should override myModule.b mock result (and leave the other methods untouched)', () => {

  myMockedModule.b.mockImplementationOnce(() => 'overridden');

  myModule.a(); // === true
  myModule.b(); // === 'overridden'
});


2 - jest.doMock(moduleName, factory, options)

2 - jest.doMock(moduleName, factory, options)

pros

优点

  • Explicitly re-mocks on every test
  • 在每次测试中明确重新模拟

cons

缺点

  • Cannot define default mock implementation for all tests
  • Cannot extend default implementation forcing to re-declare each mocked method
  • 无法为所有测试定义默认模拟实现
  • 无法扩展默认实现,强制重新声明每个模拟方法

code:

代码:

it('should override myModule.b mock result (and leave the other methods untouched)', () => {

  jest.doMock('../myModule', () => {
    return {
      a: jest.fn(() => true,
      b: jest.fn(() => 'overridden',
    }
  });

  myModule.a(); // === true
  myModule.b(); // === 'overridden'
});


3 - Manual mocking with setter methods (as explained here)

3 -手动嘲讽与setter方法(如解释在这里

pros

优点

  • Full control over mocked results
  • 完全控制模拟结果

cons

缺点

  • Lot of boilerplate code
  • Hard to maintain on long term
  • 大量样板代码
  • 难以长期维持

code:

代码:

__mocks__/myModule.js

__mocks__/myModule.js

const myMockedModule = jest.genMockFromModule('../myModule');

let a = true;
let b = true;

myMockedModule.a = jest.fn(() => a);
myMockedModule.b = jest.fn(() => b);

myMockedModule.__setA = (value) => { a = value };
myMockedModule.__setB = (value) => { b = value };
myMockedModule.__reset = () => {
  a = true;
  b = true;
};
export default myMockedModule;

__tests__/myTest.js

__tests__/myTest.js

it('should override myModule.b mock result (and leave the other methods untouched)', () => {
  myModule.__setB('overridden');

  myModule.a(); // === true
  myModule.b(); // === 'overridden'

  myModule.__reset();
});


4 - jest.spyOn(object, methodName)

4 - jest.spyOn(object, methodName)

cons

缺点

  • I can't revert back mockImplementationto the original mocked return value, therefore affecting the next tests
  • 我无法恢复mockImplementation到原来的模拟返回值,因此影响了接下来的测试

code:

代码:

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

// Mock myModule
jest.mock('../myModule');

it('should override myModule.b mock result (and leave the other methods untouched)', () => {

  const spy = jest.spyOn(myMockedModule, 'b').mockImplementation(() => 'overridden');

  myMockedModule.a(); // === true
  myMockedModule.b(); // === 'overridden'

  // How to get back to original mocked value?
});

采纳答案by user1095118

A nice pattern for writing test is to create a setup factory function that returns the data you need for testing the current module.

编写测试的一个很好的模式是创建一个设置工厂函数,该函数返回测试当前模块所需的数据。

Below is some sample code following your second example although allows the provision of default and override values in a reusable way.

下面是第二个示例之后的一些示例代码,尽管允许以可重用的方式提供默认值和覆盖值。

const spyReturns = returnValue => jest.fn(() => returnValue);

describe("scenario", () => {
  const setup = (mockOverrides) => {
    const mockedFunctions =  {
      a: spyReturns(true),
      b: spyReturns(true),
      ...mockOverrides
    }
    return {
      mockedModule: jest.doMock('../myModule', () => mockedFunctions)
    }
  }

  it("should return true for module a", () => {
    const { mockedModule } = setup();
    expect(mockedModule.a()).toEqual(true)
  });

  it("should return override for module a", () => {
    const EXPECTED_VALUE = "override"
    const { mockedModule } = setup({ a: spyReturns(EXPECTED_VALUE)});
    expect(mockedModule.a()).toEqual(EXPECTED_VALUE)
  });
});

回答by A Jar of Clay

Use mockFn.mockImplementation(fn).

使用mockFn.mockImplementation(fn)

Put the default implementation into the beforeEach. The mock will be reset to this before every test.

将默认实现放入beforeEach. 模拟将在每次测试之前重置为此。

To override, use mockImplementationin the test.

要覆盖,请mockImplementation在测试中使用。

This will override behaviour of the mock for any/all calls in the test, and will be overwritten by the beforeEachimplementation before the next test.

这将覆盖测试中任何/所有调用的模拟行为,并将beforeEach在下一个测试之前被实现覆盖。

For example:

例如:

import { funcToMock } from './somewhere';
jest.mock('./somewhere');

beforeEach(() => {
  funcToMock.mockImplementation(() => { /* default implementation */ });
});

test('case that needs a different implementation of funcToMock', () => {
  funcToMock.mockImplementation(() => { /* implementation specific to this test */ });
  // ...
});

回答by Thomas Hagstr?m

Little late to the party, but if someone else is having issues with this.

聚会有点晚,但如果其他人对此有疑问。

We use TypeScript, ES6 and babel for react-native development.

我们使用 TypeScript、ES6 和 babel 进行 react-native 开发。

We usually mock external NPM modules in the root __mocks__directory.

我们通常在根__mocks__目录中模拟外部 NPM 模块。

I wanted to override a specific function of a module in the Auth class of aws-amplify for a specific test.

我想为特定测试覆盖 aws-amplify 的 Auth 类中模块的特定功能。

    import { Auth } from 'aws-amplify';
    import GetJwtToken from './GetJwtToken';
    ...
    it('When idToken should return "123"', async () => {
      const spy = jest.spyOn(Auth, 'currentSession').mockImplementation(() => ({
        getIdToken: () => ({
          getJwtToken: () => '123',
        }),
      }));

      const result = await GetJwtToken();
      expect(result).toBe('123');
      spy.mockRestore();
    });

Gist: https://gist.github.com/thomashagstrom/e5bffe6c3e3acec592201b6892226af2

要点:https: //gist.github.com/thomashagstrom/e5bffe6c3e3acec592201b6892226af2

Tutorial: https://medium.com/p/b4ac52a005d#19c5

教程:https: //medium.com/p/b4ac52a005d#19c5