Reputation: 1872
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
// ...
}),
);
}
Upvotes: 12
Views: 11410
Reputation: 355
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);
Upvotes: 0
Reputation: 3321
I wrote the utility below which gives my codebase a partiallyMock<T>({})
call having property autocompletion for any type...
/** 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;
}
Upvotes: 2
Reputation: 1872
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>;
});
Upvotes: 1
Reputation: 59336
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
Upvotes: 7