【问题标题】:Typescript and Jest: Avoiding type errors on mocked functionsTypescript 和 Jest:避免模拟函数上的类型错误
【发布时间】:2018-12-31 21:22:00
【问题描述】:

当想用 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: 字符串,配置?:AxiosRequestConfig | undefined) => AxiosPromise'.

axios.get 的 typedef 正确地不包括 mockReturnValueOnce 属性。我们可以通过将 axios.get 包装为 Object(axios.get) 来强制 Typescript 将其视为 Object 字面量,但是:

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

【问题讨论】:

标签: node.js reactjs typescript mocking jestjs


【解决方案1】:

请使用ts-jest中的mocked函数

mocked 测试助手根据其源代码的类型为您的模拟模块甚至它们的深层方法提供类型。它利用了最新的 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的错误
  • 如果您使用 jest-preset-angular,因为它带有 ts-jest 作为依赖项,这很简单
  • 不是解决方案。应该接受以下解决方案。
  • mocked 现在已弃用,将在 28.0.0 中删除。该功能已作为 Jest 27.4.0 的一部分集成到 jest-mock 包中,请参阅github.com/facebook/jest/pull/12089。请改用 jest-mock 中的那个。
【解决方案2】:

更新到最新的 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);
});

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      添加这行代码const mockedAxios = axios as jest.Mocked&lt;typeof axios&gt;。然后使用 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);
      });
      

      【讨论】:

      • 我也用 'mockedAxios.get.getResolvedValueOnce' 尝试了这个方法,得到了 TypeError: mockedAxios.get.mockResolvedValueOnce is not a function
      • 我试过这个方法,我得到了类型为 '{ data: string; 的参数。 }' 不可分配给“Promise”类型的参数。此外,当我运行它时,我得到 mockReturnedValue 不是函数。
      • 我在使用 fetch 时必须使用 const mockedFetch = fetch as any
      • 不是解决这个问题的方法。下面的答案应该被接受。
      【解决方案5】:

      要在保持类型安全的同时惯用地模拟函数,请结合使用 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(...) 时,这需要一个 void 类型的参数
      • 我用 mockReturnValueOnce 尝试了这个方法,得到:Argument of type '{ data: string; }' 不可分配给“Promise”类型的参数。但是,测试运行并成功。然后我用 mockResolvedValueOnce( () => {data:'hello'} ) 尝试了这个,编译错误和运行时错误都解决了。
      【解决方案6】:

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

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

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

      【讨论】:

      • 我得到了 mockReturnValueOnce 不是函数
      • @Dr.G mockReturnValueOnce 在所有最新版本中都是 Jest API 的一部分。这意味着一个函数不是 Jest spy
      猜你喜欢
      • 2019-10-01
      • 1970-01-01
      • 2018-12-18
      • 2021-04-05
      • 1970-01-01
      • 2017-07-16
      • 2022-07-22
      • 2019-04-12
      • 1970-01-01
      相关资源
      最近更新 更多