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

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

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

我目前使用的是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


当前回答

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

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


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

  // ...
});

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

我们使用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

这是我在这个博客https://mikeborozdin.com/post/changing-jest-mocks-between-tests/上发现的一个很酷的方法

import { sayHello } from './say-hello';
import * as config from './config';

jest.mock('./config', () => ({
  __esModule: true,
  CAPITALIZE: null
}));

describe('say-hello', () => {
  test('Capitalizes name if config requires that', () => {
    config.CAPITALIZE = true;

    expect(sayHello('john')).toBe('Hi, John');
  });

  test('does not capitalize name if config does not require that', () => {
    config.CAPITALIZE = false;

    expect(sayHello('john')).toBe('Hi, john');
  });
});

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

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


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。

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

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

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