【问题标题】:Improve this code: How to avoid using "any" when spying on module with Jest? And how to avoid generic?改进此代码:使用 Jest 监视模块时如何避免使用“任何”?以及如何避免泛型?
【发布时间】:2022-01-27 05:33:43
【问题描述】:

我已经编写了一个 sn-p 代码,它返回一个模块,其中包含所有导出的成员作为对函数的玩笑:

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

非常方便,但有 3 点我不喜欢它:

  1. 调用 spyOnModule 时,我必须使用&lt;typeof ...&gt; 来获取返回对象中的模块方法名称。我希望能够从模块中推断出类型,而不必传递泛型。
  2. mod 是any 类型。不是那个的忠实粉丝。有模块类型吗?
  3. 我希望间谍函数保留它们的参数和返回值。现在我只是为所有这些返回&lt;any, any&gt;,因为我不确定如何获取参数和返回值类型。

这些问题可以解决吗?

【问题讨论】:

    标签: typescript jestjs typescript-generics


    【解决方案1】:
    1. 是的,只是不要指定(mod: any),否则你会用any 覆盖类型。而是使用通用参数,TS 会推断它 - 即 (mod: T)

    2. 模块只是对象,因此我们可以将 T 约束为对象,如下所示:&lt;T extends object&gt;

    3. 是的,您必须映射值,然后使用条件类型来确定哪些是函数,提取参数和返回类型(下面分别用AR 表示),哪些应该保持原样.

    旁注 - 您当前的实现将删除导出的非函数值,这是故意的吗?

    将所有这些粘在一起,这应该可以工作:

    
    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
    

    【讨论】:

    • 我收到此错误:Type 'Required&lt;T&gt;[FunctionPropertyNames&lt;T&gt;] extends (...args: any[]) =&gt; any ? SpyInstance&lt;ReturnType&lt;Required&lt;T&gt;[FunctionPropertyNames&lt;T&gt;]&gt;, ArgsType&lt;...&gt;&gt; : never' is not assignable to type 'T[Extract&lt;keyof T, string&gt;] extends (...args: infer A) =&gt; infer R ? SpyInstance&lt;R, A&gt; : T[Extract&lt;keyof T, string&gt;]'. Type 'SpyInstance&lt;ReturnType&lt;Required&lt;T&gt;[FunctionPropertyNames&lt;T&gt;]&gt;, ArgsType&lt;Required&lt;T&gt;[FunctionPropertyNames&lt;T&gt;]&gt;&gt;' is not assignable to type 'T[Extract&lt;keyof T, string&gt;] extends (...args: infer A) =&gt; infer R ? SpyInstance&lt;R, A&gt; : T[Extract&lt;keyof T, string&gt;]'.
    猜你喜欢
    • 2013-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-26
    • 2011-05-09
    • 2012-08-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多