Typescript和Jest:避免在模拟函数上键入错误

时间:2018-07-24 09:46:33

标签: node.js reactjs typescript mocking 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);
});

以上内容在Jest中运行正常,但会引发Typescript错误:

  

类型'(URL:属性'mockReturnValueOnce'不存在   字符串,配置?:AxiosRequestConfig |未定义)=> AxiosPromise'。

axios.get的typedef正确不包含mockReturnValueOnce属性。通过将axios.get包装为Object(axios.get),我们可以强制Typescript将Option Explicit #If VBA7 And Win64 Then Private Declare PtrSafe Function URLDownloadToFile Lib "urlmon" _ Alias "URLDownloadToFileA" ( _ ByVal pCaller As LongPtr, _ ByVal szURL As String, _ ByVal szFileName As String, _ ByVal dwReserved As LongPtr, _ ByVal lpfnCB As LongPtr _ ) As Long #Else Private Declare Function URLDownloadToFile Lib "urlmon" _ Alias "URLDownloadToFileA" ( _ ByVal pCaller As Long, _ ByVal szURL As String, _ ByVal szFileName As String, _ ByVal dwReserved As Long, _ ByVal lpfnCB As Long _ ) As Long #End If Public Const BINDF_GETNEWESTVERSION As Long = &H10 Public Sub GetLinks() Dim sResponse As String, html As New HTMLDocument With CreateObject("MSXML2.XMLHTTP") .Open "GET", "https://sebgroup.lu/private/luxembourg-based-funds/download-of-portfolio-holdings", False .send sResponse = StrConv(.responseBody, vbUnicode) End With sResponse = Mid$(sResponse, InStr(1, sResponse, "<!DOCTYPE ")) With html .body.innerHTML = sResponse Dim list As Object, i As Long Set list = html.getElementsByClassName("linklist")(0).getElementsByTagName("a") For i = 0 To list.Length - 1 If instr(list(i).getAttribute("href"),"SEB_") > 0 Then downloadfile list(i).getAttribute("href") End If Next i End With End Sub Public Sub downloadfile(ByVal url As String) Dim fileName As String, fileNames() As String, folderName As String fileNames = Split(url, "/") fileName = fileNames(UBound(fileNames)) folderName = "C:\Users\User\Desktop\CurrentDownloads\" & fileName '<==change as required Dim ret As Long ret = URLDownloadToFile(0, url, folderName, BINDF_GETNEWESTVERSION, 0) End Sub 视为对象文字,但是:

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

6 个答案:

答案 0 :(得分:10)

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

答案 1 :(得分:9)

要在保持类型安全的同时习惯性地模拟该功能,请结合使用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();
});

答案 2 :(得分:7)

请使用ts-jest中的mocked功能

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

答案 3 :(得分:4)

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

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

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

答案 4 :(得分:0)

@hutabalian当您使用axios.getaxios.post时,该代码非常有效,但是如果您使用config来请求以下代码,则该代码会很好地工作:

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

将导致此错误:

TS2339(TS)属性'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);
});

mediaController.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:

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

答案 5 :(得分:0)

更新到最新的 Axios (0.21.1) 后,我开始遇到这种问题。我尝试了很多解决方案,但都没有结果。

我的解决方法:

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