我写了一段代码,它返回一个模块及其所有导出的成员作为笑话监视函数:
export const spyOnModule = <T>(mod: any) => {
const obj = {} as {
[x in keyof T]: jest.SpyInstance<any, any>
}
const modKeys = Object.keys(mod)
modKeys.forEach((x) => {
const key = x as keyof T
const currentProp = mod[key]
if (typeof currentProp === 'function') {
obj[key] = jest.spyOn(mod, key as string)
}
})
return obj
}
这就是它在测试中的使用方式:
// MyComponent.test.ts
import * as LookupApi from 'Data/LookUp/LookupApi2'
import { spyOnModule } from 'helper'
const mockLookupApi = spyOnModule<typeof LookupApi>(LookupApi)
// mockLookupApi would have all the methods as spied functions
// {
// readonly useGetLookupsQuery: jest.SpyInstance<any, any>;
// readonly lookupApiReducer: jest.SpyInstance<any, any>;
// readonly lookupApiReducerPath: jest.SpyInstance<any, any>;
// readonly lookupApiMiddleware: jest.SpyInstance<...>;
// readonly lookupApiEndpoints: jest.SpyInstance<...>;
// }
describe('MyComponent',()=>{
it('Should call the api',()=>{
mockLookupApi.useGetLookupsQuery.mockReturnValue('abc')
// MyComponent act
expect(mockLookupApi.lookupApiReducerPath).toBeCalled()
expect(mockLookupApi.useGetLookupsQuery).toBeCalledWith(123)
})
})
非常方便,但有三点我不喜欢:
- 当调用spyOnModule时,我必须使用
<typeof ...>
来获取返回对象中的模块方法名称。我希望能够从模块中推断类型,而不必传递泛型 - mod是CCD_ 2类型。不太喜欢这个。模块有类型吗
- 我希望spied函数能够保留它们的参数和返回值。现在我只为所有这些返回
<any, any>
,因为我不知道如何获取参数和返回值类型
这些问题可以解决吗?
-
是的,只是不要指定
(mod: any)
,否则您会用任何类型重写类型。相反,使用一个通用参数,TS将推断它,即(mod: T)
-
模块只是对象,所以我们可以将T约束为对象,比如:
<T extends object>
-
是的,您必须映射到值上,然后使用条件类型来确定哪些是函数,提取参数和返回类型(下面分别用
A
和R
表示(,哪些应该保持原样。
附带说明-您当前的实现将删除导出的非函数值,这是有意的吗?
将这些结合在一起,这应该会奏效:
type WithModuleSpies<T extends object> = {
[key in keyof T]: T[key] extends (...args: infer A) => infer R
? jest.SpyInstance<R, A>
: T[key];
};
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
}[keyof T] &
string;
export const spyOnModule = <T extends object>(mod: T): WithModuleSpies<T> => {
const spies = { ...mod } as WithModuleSpies<T>;
for (const key in mod) {
const value = mod[key];
if (typeof value === "function") {
spies[key] = jest.spyOn(
mod,
(key as unknown) as FunctionPropertyNames<T>
);
}
}
return spies;
};
const spied = spyOnModule(MyModule);
const call = spied.foo.mock.calls[0]; // [a: number, b: number]
const instance = spied.foo.mockImplementation((a, b) => 3); // impl has type (a: number, b: number) => number | (a: number, b: number) => undefined
const value = spied.MY_CONST; // string