12

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
      // ...
    }),
  );
}

4 Answers 4

7

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
Sign up to request clarification or add additional context in comments.

3 Comments

I was still getting an error when using as and was prompted to use an unknown type instead by the compiler. Perhaps I'm using a stricter tsconfig.json I'll post the solution I found from this below. Thank you for your help.
That is really weird because unknown cannot be automatically converted to http.Response 🤔
Should not be as Promise<http.Response> as the mock function is actually returning a promise?
2

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

Demonstration of partiallyMock autocompletion in playground

Demonstration of mockWindow completion in playground

/** 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;
}

1 Comment

I was getting bad time working with jest-mock-extended, it seems out of support or something like that, and your snippet help me a lot.
1

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

Comments

0

Below is a TypeScript solution that is type-safe and allows partial return types for jest.mocked(...).

type GenericFunction = (...args: unknown[]) => unknown;
type GenericAsyncFunction = (...args: unknown[]) => Promise<unknown>;
type AwaitedReturnType<T extends GenericAsyncFunction> = Awaited<ReturnType<T>>;

type MockedFunc<T extends GenericFunction> = () => Partial<ReturnType<T>>;
type MockedFuncAsync<T extends GenericAsyncFunction> = () => Promise<Partial<AwaitedReturnType<T>>>;

export const partialMocked = <T extends MockedFunc<T>>(source: T) => jest.mocked<MockedFunc<T>>(source);
export const partialMockedAsync = <T extends MockedFuncAsync<T>>(source: T) => jest.mocked<MockedFuncAsync<T>>(source);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.