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

And*_*aro 36 javascript unit-testing mocking jestjs

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

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

  1. 模拟依赖
  2. 在单个测试中更改/扩展模拟实现
  3. 下次测试执行时,恢复原始模拟

我正在使用Jest v21.

以下是典型的Jest测试:

__mocks__/myModule.js

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

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

export default myMockedModule;
Run Code Online (Sandbox Code Playgroud)

__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
  });
});
Run Code Online (Sandbox Code Playgroud)

我尝试了一些策略,但我找不到任何可以定义令人满意的解决方案.


1 - mockFn.mockImplementationOnce(fn)

利弊

  • 第一次调用后恢复原始实现

缺点

  • 如果测试调用b更多次,它将会中断
  • b没有调用之前它不会恢复到原始实现(在下一个测试中泄漏)

码:

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

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

  myModule.a(); // === true
  myModule.b(); // === 'overridden'
});
Run Code Online (Sandbox Code Playgroud)

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

利弊

  • 每次测试都会重新嘲笑

缺点

  • 无法为所有测试定义默认模拟实现
  • 无法扩展默认实现强制重新声明每个模拟方法

码:

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'
});
Run Code Online (Sandbox Code Playgroud)

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

利弊

  • 完全控制模拟结果

缺点

  • 很多样板代码
  • 很难长期保持健康

码:

__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;
Run Code Online (Sandbox Code Playgroud)

__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();
});
Run Code Online (Sandbox Code Playgroud)

4 - jest.spyOn(object,methodName)

缺点

  • 我无法恢复mockImplementation到原始的模拟返回值,因此影响以下测试

码:

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?
});
Run Code Online (Sandbox Code Playgroud)

提前感谢您的任何意见/建议!

use*_*118 20

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

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

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)
  });
});
Run Code Online (Sandbox Code Playgroud)


A J*_*lay 12

使用mockFn.mockImplementation(fn)

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

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

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

例如:

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 */ });
  // ...
});
Run Code Online (Sandbox Code Playgroud)

  • 我不喜欢这个答案,因为它需要复制您想要模拟的函数的逻辑。如果更改源函数,您现在必须记住更新每个用其实际逻辑模拟其实现的测试类。 (4认同)

Tho*_*röm 8

晚会晚了一点,但是如果有人对此有疑问。

我们使用TypeScript,ES6和babel进行本机开发。

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

我想覆盖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();
    });
Run Code Online (Sandbox Code Playgroud)

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

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


Den*_*s P 6

当模拟单个方法时(当需要保持类/模块实现的其余部分完好无损时),我发现以下方法有助于重置单个测试中的任何实现调整。

我发现这种方法是最简洁的方法,不需要jest.mock在文件开头等处添加任何内容。您只需要下面看到的代码来模拟MyClass.methodName。另一个优点是,默认情况下spyOn保留原始方法实现,但也保存所有统计信息(调用、参数、结果等)以进行测试,并且在某些情况下必须保留默认实现。因此,您可以灵活地保留默认实现或通过简单添加来更改它,.mockImplementation如下面的代码中所述。

代码采用 Typescript 编写,注释突出显示了 JS 的差异(准确地说,差异在一行中)。使用 Jest 26.6 进行测试。

describe('test set', () => {
    let mockedFn: jest.SpyInstance<void>; // void is the return value of the mocked function, change as necessary
    // For plain JS use just: let mockedFn;

    beforeEach(() => {
        mockedFn = jest.spyOn(MyClass.prototype, 'methodName');
        // Use the following instead if you need not to just spy but also to replace the default method implementation:
        // mockedFn = jest.spyOn(MyClass.prototype, 'methodName').mockImplementation(() => {/*custom implementation*/});
    });

    afterEach(() => {
        // Reset to the original method implementation (non-mocked) and clear all the mock data
        mockedFn.mockRestore();
    });

    it('does first thing', () => {
        /* Test with the default mock implementation */
    });

    it('does second thing', () => {
        mockedFn.mockImplementation(() => {/*custom implementation just for this test*/});
        /* Test utilising this custom mock implementation. It is reset after the test. */
    });

    it('does third thing', () => {
        /* Another test with the default mock implementation */
    });
});
Run Code Online (Sandbox Code Playgroud)