Машинопись и шутка: избегайте ошибок типа на издевающихся функциях

Когда вы хотите издеваться над внешними модулями с Jest, мы можем использовать метод jest.mock() для автоматического jest.mock() функций на модуле.

Затем мы можем манипулировать и опросить насмешливые функции на нашем издеваемом модуле по своему желанию.

Например, рассмотрим следующий надуманный пример для издевательств над модулем axios:

import myModuleThatCallsAxios from '../myModule';
import axios from 'axios';

jest.mock('axios');

it('Calls the GET method as expected', async () => {
  const expectedResult: string = 'result';

  axios.get.mockReturnValueOnce({ data: expectedResult });
  const result = await myModuleThatCallsAxios.makeGetRequest();

  expect(axios.get).toHaveBeenCalled();
  expect(result).toBe(expectedResult);
});

Вышеупомянутое будет отлично работать в Jest, но будет вызывать ошибку TypScript:

Свойство 'mockReturnValueOnce' не существует в типе '(url: string, config?: AxiosRequestConfig | undefined) => AxiosPromise'.

В typedef для axios.get праву не входит свойство mockReturnValueOnce. Мы можем заставить axios.get обрабатывать axios.get как литерал объекта, обернув его как Object(axios.get), но:

Каков идиоматический способ издеваться над функциями при сохранении безопасности типов?

Ответ 1

Добавьте эту строку кода const mockedAxios = axios as jest.Mocked<typeof axios>. А затем используйте mockedAxios для вызова mockReturnValueOnce. С вашим кодом должно быть сделано так:

import myModuleThatCallsAxios from '../myModule';
import axios from 'axios';

jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

it('Calls the GET method as expected', async () => {
  const expectedResult: string = 'result';

  mockedAxios.get.mockReturnValueOnce({ data: expectedResult });
  const result = await myModuleThatCallsAxios.makeGetRequest();

  expect(mockedAxios.get).toHaveBeenCalled();
  expect(result).toBe(expectedResult);
});

Ответ 2

Для идиоматического макета функции при сохранении безопасности типов используйте spyOn в сочетании с mockReturnValueOnce:

import myModuleThatCallsAxios from '../myModule';
import axios from 'axios';

it('Calls the GET method as expected', async () => {
  const expectedResult: string = 'result';

  // set up mock for axios.get
  const mock = jest.spyOn(axios, 'get');
  mock.mockReturnValueOnce({ data: expectedResult });

  const result = await myModuleThatCallsAxios.makeGetRequest();

  expect(mock).toHaveBeenCalled();
  expect(result).toBe(expectedResult);

  // restore axios.get
  mock.mockRestore();
});

Ответ 3

Обычный подход для предоставления новых функциональных возможностей для импорта для расширения исходного модуля, такого как declare module "axios" {... }. Это не лучший выбор здесь, потому что это должно быть сделано для всего модуля, в то время как mocks может быть доступен в одном тесте и быть недоступным в другом.

В этом случае подход, основанный на типе, заключается в утверждении типов, где это необходимо:

  (axios.get as jest.Mock).mockReturnValueOnce({ data: expectedResult });
  ...
  expect(axios.get as jest.Mock).toHaveBeenCalled();

Ответ 4

Пожалуйста, используйте функцию mocked из ts-jest

import myModuleThatCallsAxios from '../myModule';
import axios from 'axios';

jest.mock('axios');

// OPTION - 1
const mockedAxios = mocked(axios, true)
// your original 'it' block
it('Calls the GET method as expected', async () => {
  const expectedResult: string = 'result';

  mockedAxios.mockReturnValueOnce({ data: expectedResult });
  const result = await myModuleThatCallsAxios.makeGetRequest();

  expect(mockedAxios.get).toHaveBeenCalled();
  expect(result).toBe(expectedResult);
});

// OPTION - 2
// wrap axios in mocked at the place you use
it('Calls the GET method as expected', async () => {
  const expectedResult: string = 'result';

  mocked(axios).get.mockReturnValueOnce({ data: expectedResult });
  const result = await myModuleThatCallsAxios.makeGetRequest();

  // notice how axios is wrapped in 'mocked' call
  expect(mocked(axios).get).toHaveBeenCalled();
  expect(result).toBe(expectedResult);
});

Я не могу подчеркнуть, насколько велик mocked, больше не было кастинга.

Ответ 5

Будет ли это работать так же с функцией? Я пытаюсь смоделировать node-fetch безуспешно :(