我想在每个测试基础上更改模拟依赖的实现,方法是扩展默认模拟的行为,并在下一次测试执行时将其恢复到原始实现。

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

模拟的依赖 在单个测试中更改/扩展模拟实现 在执行下一次测试时恢复到原始模拟

我目前使用的是Jest v21。下面是一个典型的测试:

// __mocks__/myModule.js

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

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

export default myMockedModule;
// __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
  });
});

以下是我目前为止尝试过的方法:

mockFn.mockImplementationOnce(fn) it('should override myModule.b mock result (and leave the other methods untouched)', () => { myMockedModule.b.mockImplementationOnce(() => 'overridden'); myModule.a(); // === true myModule.b(); // === 'overridden' }); Pros Reverts back to original implementation after first call Cons It breaks if the test calls b multiple times It doesn't revert to original implementation until b is not called (leaking out in the next test) 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' }); 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 Manual mocking with setter methods (as explained here) // __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 it('should override myModule.b mock result (and leave the other methods untouched)', () => { myModule.__setB('overridden'); myModule.a(); // === true myModule.b(); // === 'overridden' myModule.__reset(); }); Pros Full control over mocked results Cons Lot of boilerplate code Hard to maintain on long term jest.spyOn(object, methodName) 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? }); Cons I can't revert mockImplementation back to the original mocked return value, therefore affecting the next tests


当前回答

我没有设法在测试本身中定义模拟,所以我发现我可以模拟相同服务模拟的几个结果,如下所示:

jest.mock("@/services/ApiService", () => {
    return {
        apiService: {
            get: jest.fn()
                    .mockResolvedValueOnce({response: {value:"Value", label:"Test"}})
                    .mockResolvedValueOnce(null),
        }
    };
});

我希望它能帮助到一些人:)

其他回答

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

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


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

describe("scenario", () => {
  beforeEach(() => {
    jest.resetModules();
  });

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

  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)
  });
});

必须使用jest.resetModules()重置缓存的模块,这一点很重要。这可以在beforeEach或类似的拆卸函数中完成。

更多信息请参见笑话对象文档:https://jestjs.io/docs/jest-object。

使用mockFn.mockImplementation (fn)。

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

beforeEach(() => {
  funcToMock.mockImplementation(() => { /* default implementation */ });
  // (funcToMock as jest.Mock)... in TS
});

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

  // ...
});

我没有设法在测试本身中定义模拟,所以我发现我可以模拟相同服务模拟的几个结果,如下所示:

jest.mock("@/services/ApiService", () => {
    return {
        apiService: {
            get: jest.fn()
                    .mockResolvedValueOnce({response: {value:"Value", label:"Test"}})
                    .mockResolvedValueOnce(null),
        }
    };
});

我希望它能帮助到一些人:)

有点晚了,但如果有人对此有意见。

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

我们通常在根目录__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();
    });

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

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

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

I found this approach to be the concisest one, with no need to jest.mock something at the beginning of the file etc. You need just the code you see below to mock MyClass.methodName. Another advantage is that by default spyOn keeps the original method implementation but also saves all the stats (# of calls, arguments, results etc.) to test against, and keeping the default implementation is a must in some cases. So you have the flexibility to keep the default implementation or to change it with a simple addition of .mockImplementation as mentioned in the code below.

代码是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 */
    });
});