Как изменить макетную реализацию на отдельной тестовой основе [Jestjs]

Я бы хотел изменить реализацию ложной зависимости на основе одного теста, расширив стандартное поведение по умолчанию и вернув его обратно < к исходной реализации, когда выполняется следующий тест.

Короче, вот чего я пытаюсь достичь:

  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;

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

Я испробовал несколько стратегий, но не нашел решения, которое мог бы определить удовлетворяющее.


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

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

3 - Ручная насмешка с использованием методов установки (как описано здесь)

плюсы

  • Полный контроль над поддельными результатами

минусы

  • Лот шаблонного кода
  • Трудно поддерживать в долгосрочной перспективе

Код:

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

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 the original mocked value?
});

Ответ 1

Хорошим шаблоном для написания теста является создание функции фабрики настроек, которая возвращает данные, необходимые для тестирования текущего модуля.

Ниже приведен пример кода, следующий за вашим вторым примером, хотя он позволяет предоставлять значения по умолчанию и переопределять значения повторно.

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

Ответ 2

Используйте 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 */ });
  // ...
});

Ответ 3

Немного опоздал на вечеринку, но если у кого-то еще возникли проблемы с этим.

Мы используем TypeScript, ES6 и babel для быстрой разработки.

Обычно мы высмеиваем внешние модули NPM в корневом каталоге __mocks__.

Я хотел переопределить определенную функцию модуля в классе Auth aws-ampify для конкретного теста.

    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://medium.com/p/b4ac52a005d#19c5