简体   繁体   中英

How to allow partial TypeScript types when mocking functions - Jest, TypeScript

I have a function which I would like to mock for testing purposes in TypeScript. In my tests, all I care about are the json and the status . However, when using Jest's jest.spyOn the type of my mocked function is set to return a http Response type. This is awkward as it means I have to manually go and implement a bunch of functions and properties that are irrelevant and arbitrary.

I suspect there is some way to use a partial type here to allow better and more useful mocking by overriding the return type to only that I care about. How would I go about doing this?

export function mockApi(json: object, status: number): void {
  jest.spyOn(
    myApiModule,
    'methodWhichReturnsAResponse'
  ).mockImplementation(() =>
    Promise.resolve({
      json: () => Promise.resolve(json),
      status,
      // Below here is to appease jest types (not needed for
      // testing purposes at the time of writing)
      headers: {
        has: (name: string) => true,
        // get, set, etc...
      },
      ok: true,
      redirected: false,
      // and about 10 other properties which exist on the Response type
      // ...
    }),
  );
}

You can use as ...

export function mockApi(json: object, status: number): void {
  jest.spyOn(
    myApiModule,
    'methodWhichReturnsAResponse'
  ).mockImplementation(() =>
    Promise.resolve({
      json: () => Promise.resolve(json),
      status
    } as http.Response), // <-- here
  );
}

The as keyword, used for typecasting, when used to convert a literal to type X, will allow you to define it only partially, but you still have type-checking because you cannot define props that don't exist.

Example:

type X {
  a: number
  b: number
}

const x = { a: 2 } as X // OK
const y = { a: 3, c: 2 } as X // NOT OK, because c does not exist in X

I found a solution using the unknown type.

After trying and failing to use as to typecast immediately, I first cast the promise to unknown and then cast this value to the desired Response type like so:

    // ...
    .mockImplementation(() => {
      const httpResponsePromise = Promise.resolve({
        json: () => Promise.resolve(json),
        status,
      }) as unknown;
      return httpResponsePromise as Promise<Response>;
    });

I wrote the utility below which gives my codebase a partiallyMock<T>({}) call having property autocompletion for any type...

在 Playground 中演示 partialMock 自动完成

在操场上演示 mockWindow 完成

/** Simple mocking inspired by https://www.npmjs.com/package/jest-mock-extended
 * which has mockDeep<T>() for excellent autocompletion support but had other issues. */

/* atomic values (not made Partial when mocking) */
type Atomic = boolean | string | number | symbol | Date;

/** Mocks an indexed type (e.g. Object or Array), making it recursively Partial - note question mark  */
type PartialMockIndexed<T> = {
  [P in keyof T]?: PartialMock<T[P]>;
};

/** Mock any T */
export type PartialMock<T> = T extends Atomic ? T : PartialMockIndexed<T>;

/** Utility method for autocompleting a PartialMock<T> and returning it as a T */
export function partiallyMock<T>(mock: PartialMock<T>) {
  return mock as T;
}

/** Window is a special object, needs special mocking */
export function mockWindow(windowMock: PartialMock<typeof window>) {
  const origWindow = window;
  globalThis.window = Object.create(window);
  for (const [key, value] of Object.entries(windowMock)) {
    Object.defineProperty(globalThis.window, key, { value });
  }
  const unmockWindow = (globalThis.window = origWindow);
  return unmockWindow;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM