Как изменить реализацию макета для каждого отдельного теста [Jestjs]

86

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

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

  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 (имя модуля, завод, параметры)

плюсы

  • Явно перепроверяет имитацию при каждом тесте

минусы

  • Невозможно определить реализацию макета по умолчанию для всех тестов
  • Невозможно расширить реализацию по умолчанию, заставив повторно объявить каждый фиктивный метод

код:

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 (объект, имя метода)

минусы

  • Я не могу вернуться 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?
});
Андреа Карраро
источник
Ницца. Но как сделать вариант 2 для модуля npm, такого как @ private-repo / module? Большинство примеров, которые я вижу, имеют относительные пути? Это работает и для установленных модулей?
mrbinky3000

Ответы:

47

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

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

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)
  });
});
user1095118
источник
40

Ванильный JS

Используйте mockFn.mockImplementation (fn) .

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 */ });
  // ...
});

Машинопись

Чтобы сообщение mockImplementation не было свойством funcToMock , вам необходимо указать тип, например, изменив верхнюю строку сверху на следующую:

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

Вопрос, касающийся этой проблемы, можно найти здесь: макет свойства jest typescript не существует для типа

Баночка с глиной
источник
21

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

Мы используем 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();
    });

Суть: https://gist.github.com/thomashagstrom/e5bffe6c3e3acec592201b6892226af2

Учебник: https://medium.com/p/b4ac52a005d#19c5

Томас Хагстрём
источник