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

dun*_*all 15 mocking node.js typescript reactjs jestjs

当想要使用Jest模拟外部模块时,我们可以使用该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);
});
Run Code Online (Sandbox Code Playgroud)

以上将在Jest中正常运行,但会抛出一个Typescript错误:

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

typedef axios.get正确不包含mockReturnValueOnce属性.我们可以强制将Typescript axios.get包装为Object文字Object(axios.get),但是:

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

小智 32

添加这行代码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);
});
Run Code Online (Sandbox Code Playgroud)

  • 我还尝试使用“mockedAxios.get.getResolvedValueOnce”使用此方法并得到 TypeError:mockedAxios.get.mockResolvedValueOnce 不是函数 (10认同)
  • 我尝试了这种方法,得到了类型为 '{ data: string; 的参数;}' 不可分配给类型为“Promise&lt;unknown&gt;”的参数。另外,当我运行它时,我得到mockReturnedValue is not a function。 (5认同)

Mel*_*hia 14

mocked从ts-jest 27.0 开始,ts-jest将在 28.0 中弃用并删除,您可以在官方文档中查看。所以请使用jest.mockedfrom代替jest。这是文档

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

所以对于你的例子:

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

Run Code Online (Sandbox Code Playgroud)


Bri*_*ams 12

要在保持类型安全的同时习惯性地模拟该函数,请使用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();
});
Run Code Online (Sandbox Code Playgroud)

  • 缺少打字稿实施 (4认同)

Est*_*ask 6

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

在这种情况下,类型安全的方法是在需要时声明类型:

  (axios.get as jest.Mock).mockReturnValueOnce({ data: expectedResult });
  ...
  expect(axios.get as jest.Mock).toHaveBeenCalled();
Run Code Online (Sandbox Code Playgroud)


Ogg*_*las 6

axios.get@hutabalian 当您使用or时,代码效果非常好axios.post,但如果您使用configfor 请求,则以下代码:

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

会导致这个错误:

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

你可以这样解决:

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);
});
Run Code Online (Sandbox Code Playgroud)

媒体控制器.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');
}
Run Code Online (Sandbox Code Playgroud)

请求.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))
}
Run Code Online (Sandbox Code Playgroud)


小智 5

请使用以下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);
});
Run Code Online (Sandbox Code Playgroud)

我无法强调有多伟大mocked,再也没有类型转换。

  • mocked 现已弃用,并将在 28.0.0 中删除。该功能已作为 Jest 27.4.0 的一部分集成到 jest-mock 包中,请参阅 https://github.com/facebook/jest/pull/12089。请改用 jest-mock 中的一个。 (11认同)
  • 我只收到一个错误“TypeError: ts_jest_1.mocked(...).sendMessage.mockReturnValue is not a function” (5认同)
  • 更新了模拟文档的链接:https://kulshekhar.github.io/ts-jest/docs/guides/test-helpers/ (3认同)