简体   繁体   English

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

[英]Typescript and Jest: Avoiding type errors on mocked functions

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'.类型“(url: string, config?: AxiosRequestConfig | undefined) => AxiosPromise”上不存在属性“mockReturnValueOnce”。

The typedef for axios.get rightly doesn't include a mockReturnValueOnce property. axios.get的 typedef 正确地不包含mockReturnValueOnce属性。 We can force Typescript to treat axios.get as an Object literal by wrapping it as Object(axios.get) , but:我们可以通过将 axios.get 包装为Object(axios.get)来强制 Typescript 将axios.get视为对象文字,但是:

What is the idiomatic way to mock functions while maintaining type safety?在保持类型安全的同时模拟函数的惯用方法是什么?

Add this line of code const mockedAxios = axios as jest.Mocked<typeof axios> .添加这行代码const mockedAxios = axios as jest.Mocked<typeof axios> And then use the mockedAxios to call the mockReturnValueOnce.然后使用 mockedAxios 调用 mockReturnValueOnce。 With your code, should be done like this:使用您的代码,应该这样做:

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

Please use mocked function 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 mocked is, no more type-casting ever.我不能强调mocked是多么伟大,再也没有类型转换了。

To idiomatically mock the function while maintaining type safety use spyOn in 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();
});

A usual approach to provide new functionality to imports to extend original module like declare module "axios" { ... } .为导入提供新功能以扩展原始模块的常用方法,如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.这在这里不是最佳选择,因为这应该针对整个模块完成,而模拟可能在一个测试中可用而在另一个测试中不可用。

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

Starting with ts-jest 27.0 mocked from ts-jest will be deprecated and removed in 28.0 you can check it in the official documentation .从 ts-jest 27.0 开始,模拟自 ts- mockedts-jest将在 28.0 中被弃用和删除,您可以在官方文档中查看。 So please use instead jest.mocked from jest .所以请改用jest中的jest.mocked Here's the documentation这是文档

来自 ts-jest 的嘲笑将在 28.0 中被弃用和删除

So for your example:所以对于你的例子:

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

jest.mock('axios');

// OPTION - 1
const mockedAxios = jest.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);
});

@hutabalian The code works really well when you use axios.get or axios.post but if you use a config for requests the following code: @hutabalian 当您使用axios.getaxios.post时,该代码非常有效,但如果您使用config请求以下代码:

const expectedResult: string = 'result';
const mockedAxios = axios as jest.Mocked<typeof axios>;
mockedAxios.mockReturnValueOnce({ data: expectedResult });

Will result in this error:会导致这个错误:

TS2339 (TS) Property 'mockReturnValueOnce' does not exist on type 'Mocked'. TS2339 (TS) 属性“mockReturnValueOnce”在“Mocked”类型上不存在。

You can solve it like this instead:你可以这样解决:

AxiosRequest.test.tsx AxiosRequest.test.tsx

import axios from 'axios';
import { MediaByIdentifier } from '../api/mediaController';

jest.mock('axios', () => jest.fn());

test('Test AxiosRequest',async () => {
    const mRes = { status: 200, data: 'fake data' };
    (axios as unknown as jest.Mock).mockResolvedValueOnce(mRes);
    const mock = await MediaByIdentifier('Test');
    expect(mock).toEqual(mRes);
    expect(axios).toHaveBeenCalledTimes(1);
});

mediaController.ts:媒体控制器.ts:

import { sendRequest } from './request'
import { AxiosPromise } from 'axios'
import { MediaDto } from './../model/typegen/mediaDto';

const path = '/api/media/'

export const MediaByIdentifier = (identifier: string): AxiosPromise<MediaDto> => {
    return sendRequest(path + 'MediaByIdentifier?identifier=' + identifier, 'get');
}

request.ts:请求.ts:

import axios, { AxiosPromise, AxiosRequestConfig, Method } from 'axios';

const getConfig = (url: string, method: Method, params?: any, data?: any) => {
     const config: AxiosRequestConfig = {
         url: url,
         method: method,
         responseType: 'json',
         params: params,
         data: data,
         headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/json' },
    }
    return config;
}

export const sendRequest = (url: string, method: Method, params?: any, data?: any): AxiosPromise<any> => {
    return axios(getConfig(url, method, params, data))
}

After updating to the newest Axios (0.21.1) I started to have this kind of problem.更新到最新的 Axios (0.21.1) 后,我开始遇到这种问题。 I tried a lot of solutions but with no result.我尝试了很多解决方案,但没有结果。

My workaround:我的解决方法:

type axiosTestResponse = (T: unknown) => Promise<typeof T>;

...

it('some example', async () => {
  const axiosObject = {
    data: { items: [] },
    status: 200,
    statusText: 'ok',
    headers: '',
    config: {},
  } as AxiosResponse;

  (Axios.get as axiosTestResponse) = () => Promise.resolve(axiosObject);
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM