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

IT技术 node.js reactjs typescript mocking jestjs
2021-03-31 17:06:09

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

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

例如,考虑以下模拟 axios module的人为示例:

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 中运行良好,但会抛出 Typescript 错误:

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

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

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

6个回答

添加这行代码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);
});
const mockedFetch = fetch as any在使用 fetch 时必须使用
2021-05-24 17:06:09
我试过这个方法,我得到了类型为 '{ data: string; 的参数;}' 不可分配给类型为 'Promise<unknown>' 的参数。此外,当我运行它时,我得到 mockReturnedValue 不是一个函数。
2021-05-27 17:06:09
我也用'mockedAxios.get.getResolvedValueOnce'尝试了这个方法并得到了TypeError:mockedAxios.get.mockResolvedValueOnce不是一个函数
2021-06-10 17:06:09
不是此问题的解决方案。下面的答案应该被接受。
2021-06-21 17:06:09

请使用mocked功能从ts-jest

mocked测试助手根据其源的类型提供对模拟module的类型,甚至是它们的深层方法。它利用了最新的 TypeScript 功能,因此您甚至可以在 IDE 中完成参数类型(而不是 jest.MockInstance)。

import myModuleThatCallsAxios from '../myModule';
import axios from 'axios';
import { mocked } from 'ts-jest/utils'

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,再也没有类型转换了。

我只收到一个错误 TypeError: ts_jest_1.mocked(...).sendMessage.mockReturnValue is not a function
2021-05-23 17:06:09
2021-05-25 17:06:09
不是解决方案。下面的解决方案应该被接受。
2021-06-18 17:06:09
如果您使用 jest-preset-angular,因为它与 ts-jest 作为依赖项一起使用,这很简单
2021-06-20 17:06:09

要在保持类型安全的同时使用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();
});
当您设置 mockReturnValueOnce(...)
2021-06-06 17:06:09
缺少typescript实现
2021-06-11 17:06:09
我用 mockReturnValueOnce 尝试了这个方法并得到: '{ data: string; 类型的参数;}' 不可分配给类型为 'Promise<unknown>' 的参数。但是,测试运行并成功。然后我用 mockResolvedValueOnce( () => {data:'hello'} ) 尝试了这个,编译错误和运行时错误都解决了。
2021-06-16 17:06:09

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

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

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

@hutabalian 当您使用axios.getoraxios.post但如果您使用configfor 请求以下代码时,该代码工作得非常好

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

会导致这个错误:

TS2339 (TS) 属性“mockReturnValueOnce”在“Mocked”类型上不存在。

你可以这样解决:

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

媒体控制器.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');
}

请求.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))
}