Typescript 和 Jest:避免模拟函数的类型错误

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/51495473/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 05:32:39  来源:igfitidea点击:

Typescript and Jest: Avoiding type errors on mocked functions

node.jsreactjstypescriptmockingjestjs

提问by duncanhall

When wanting to mock external modules with Jest, we can use the jest.mock()method to auto-mock functions on a module.

当想用 Jest 模拟外部模块时,我们可以使用该jest.mock()方法在模块上自动模拟功能。

We can then manipulate and interrogate the mocked functions on our mocked module as we wish.

然后,我们可以根据需要在模拟模块上操作和询问模拟函数。

For example, consider the following contrived example for mocking the axios module:

例如,考虑以下模拟 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);
});

The above will run fine in Jest but will throw a Typescript error:

上面的代码在 Jest 中运行良好,但会抛出 Typescript 错误:

Property 'mockReturnValueOnce' does not exist on type '(url: string, config?: AxiosRequestConfig | undefined) => AxiosPromise'.

属性 'mockReturnValueOnce' 在类型 '(url: string, config?: AxiosRequestConfig | undefined) => AxiosPromise' 上不存在。

The typedef for axios.getrightly doesn't include a mockReturnValueOnceproperty. We can force Typescript to treat axios.getas an Object literal by wrapping it as Object(axios.get), but:

rightly 的 typedefaxios.get不包含mockReturnValueOnce属性。我们可以axios.get通过将Typescript包装为来强制 Typescript 将其视为对象文字Object(axios.get),但是:

What is the idiomatic way to mock functions while maintaining type safety?

在保持类型安全的同时模拟函数的惯用方法是什么?

回答by hutabalian

Add this line of code const mockedAxios = axios as jest.Mocked<typeof axios>. And then use the mockedAxios to call the mockReturnValueOnce. With your code, should be done like this:

添加这行代码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);
});

回答by Ankeet Maini

Please use mockedfunction from ts-jest

请使用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);
});

I can't emphasise how great mockedis, no more type-casting ever.

我不能强调它有多棒mocked,再也没有类型转换了。

回答by Brian Adams

To idiomatically mock the function while maintaining type safety use spyOnin combination with mockReturnValueOnce:

要在保持类型安全的同时使用spyOnmockReturnValueOnce来惯用地模拟函数:

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

回答by Estus Flask

A usual approach to provide new functionality to imports to extend original module like declare module "axios" { ... }. It's not the best choice here because this should be done for entire module, while mocks may be available in one test and be unavailable in another.

为导入提供新功能以扩展原始模块(如declare module "axios" { ... }. 这在这里不是最佳选择,因为这应该针对整个模块完成,而模拟可能在一个测试中可用而在另一个测试中不可用。

In this case a type-safe approach is to assert types where needed:

在这种情况下,类型安全的方法是在需要的地方断言类型:

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