Jest TypeScript:属性模拟在类型上不存在

87

使用 jest.fn() 添加模拟时,您通常可以访问 .mock 属性以访问调用等详细信息,类似于下面这样:

test('not working', () => {
    const foo = new Foo();
    foo.addListener = jest.fn();
    foo.func(); // will call addListener with a callback
    const callback = foo.addListener.mock.calls[0][0];
    expect(callback()).toEqual(1); // test the callback
});

当我使用TypeScript而不是纯JavaScript来实施测试时,我会遇到以下错误:

error TS2339: 属性“mock”在类型“(callback: () => number) => void”上不存在。

我可以通过强制转换为any来摆脱这个错误,但肯定有更好的方法:

const callback = (foo.addListener as any).mock.calls[0][0];

在这个简单的代码中,模拟可以重新编写以使用 jest.fn(fn => { callback = fn; }); 存储参数,但是当使用 foo.addListener.mockClear() 时会出现同样的错误,无法以相同的方式重写它。

那么我该如何消除错误,最好不要失去类型安全性?

4个回答

105

对于任何人来说,比将其强制转换为any类型更好的方法可能是将其转换为jest.Mock类型。

const callback = (foo.addListener as jest.Mock).mock.calls[0][0];

2021年9月更新

为了获得既符合模拟函数类型又符合Jest模拟类型jest.MockedFunction的模拟函数,可以使用它:

const addListenerMock = addListener as jest.MockedFunction<typeof addListener>;

61

您可以使用 jest.spyOn 与类似mockImplementation的函数结合使用,以在 TypeScript 中保留类型安全性的同时模拟函数:

class Foo {
  addListener = (callback: () => number) => { }
  func = () => {
    this.addListener(() => 1);
  }
}

test('working', () => {
  const foo = new Foo();
  const mockAddListener = jest.spyOn(foo, 'addListener'); // spy on foo.addListener
  mockAddListener.mockImplementation(() => { }); // replace the implementation if desired
  foo.func(); // will call addListener with a callback
  const callback = mockAddListener.mock.calls[0][0];
  expect(callback()).toEqual(1); // SUCCESS
});

24
在测试中,是否有一种方法可以模拟类中的函数而不必初始化该函数? - Naguib Ihab
这种方法的唯一问题是,你需要像 import * as _ from '' 一样导入整个库,因为间谍被应用于一个对象。 - windmaomao

16

在使用axios时,遇到以下错误:

TS2339(TS)类型“AxiosStatic”上不存在属性“mockResolvedValueOnce”

尝试使用axios as jest.Mock,但是出现以下错误:

TS2352(TS)将类型“AxiosStatic”转换为类型“Mock<any,any>”可能是一个错误, 因为两种类型都没有足够的重叠。如果这是有意的,请先将表达式转换为“unknown”。 类型“AxiosStatic”缺少类型“Mock<any,any>”的以下属性:getMockName、 mock、mockClear、mockReset以及其他12个。

通过指定axios as unknown as jest.Mock来解决此问题。

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

有人可能会执行 axios.get() - windmaomao

7

jest.spyOn 是正确的方法。

然而,如果你想对一个非类成员函数进行同样的操作,可以使用 jest.mocked。以下是一个从 Firebase 中导入函数的示例:

/** Example code depending on a simple function */
import {
  addDoc,  // Function to be mocked
  collection,
  getFirestore
} from 'firebase/firestore';

export async function createRecord(record: any) {
  const collectionRef = collection(getFirestore(), 'example-path');

  return addDoc(collectionRef, record);
}

/** Unit tests for example code */
import { addDoc } from 'firebase/firestore';

jest.mock('firebase/firestore');

describe('Create record', () => {
  test('creates a new record', async () => {
    const mockedAddDoc = jest.mocked(addDoc);

    await createRecord({ hello: 'world' });

    expect(mockedAddDoc.mock.calls[0][0]).toMatchObject({ hello: 'world' });
  });
});

https://jestjs.io/docs/jest-object#jestmockedtitem-t-deep--false


2
这里的关键是 jest.mocked(dependency) 的提示。 - fredrivett
jest.mocked(dependency) 对我很有用! - Henrique Aron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接