31

What would be my absolute easiest way of mocking fetch using Typescript?

I would just like to do something simple like below. But Typescript tells me that I'm not matching the definition for the full fetch object.

Type 'Mock<Promise<{ json: () => Promise<{ test: number; }>; }>, []>' is not assignable to type '(input: RequestInfo, init?: RequestInit | undefined) => Promise<Response>'.
   Type 'Promise<{ json: () => Promise<{ test: number; }>; }>' is not assignable to type 'Promise<Response>'.
     Type '{ json: () => Promise<{ test: number; }>; }' is missing the following properties from type 'Response': headers, ok, redirected, status, and 11 more.

What would be the simplest solution to get around this? Actually mock out the whole fetch object or other solution?

global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve({ test: 100 }),
  }),
)
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Riri
  • 11,501
  • 14
  • 63
  • 88
  • 1
    What exactly does *"TypeScript goes crazy"* mean? Presumably an error that your test double doesn't have the same interface as the actual fetch. Have you considered a [type assertion](https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions)? – jonrsharpe Nov 13 '20 at 09:22
  • 2
    TS tells me that I'm not matching the full definition for fetch - that's fine. @jonrsharpe can typer assertion get me around that? `Type 'Mock Promise<{ test: number; }>; }>, []>' is not assignable to type '(input: RequestInfo, init?: RequestInit | undefined) => Promise'. Type 'Promise<{ json: () => Promise<{ test: number; }>; }>' is not assignable to type 'Promise'. Type '{ json: () => Promise<{ test: number; }>; }' is missing the following properties from type 'Response': headers, ok, redirected, status, and 11 more.` – Riri Nov 13 '20 at 09:41

3 Answers3

86

You can tell TypeScript that you're defining global.fetch as a Jest mock.

global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve({ test: 100 }),
  }),
) as jest.Mock;
ourmaninamsterdam
  • 1,915
  • 15
  • 11
  • 3
    man I wasted 2 days on this, Thanks... – gsb22 Oct 13 '21 at 13:39
  • 14
    you can also use this, which is similar: jest.spyOn(global, "fetch").mockImplementation( jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ data: 100 }), }), ) as jest.Mock ) – philomath Nov 29 '21 at 05:20
  • 2
    @GreatQuestion I feel yours is a better implementation using spyOn. I don't know why but using spyOn gives much less errors undefined errors than simply using jest.fn(). It would have been better if you posted it as a separate answer. – EternalObserver May 14 '22 at 16:41
  • if i running other tests does it impact them also? or it just bound to the test scope? – Matan Tubul Feb 13 '23 at 08:11
6

I had some problems using the before approaches, here is how I work around that:

First my test code:

describe("Testing the Assets Service", () => {
let fetchMock: any = undefined;

beforeEach(() => {
    fetchMock = jest.spyOn(global, "fetch")
    .mockImplementation(assetsFetchMock);
});

afterEach(() => {
    jest.restoreAllMocks();
});

test('Fetch has been called', () => {
    const baseUrl = "https://myurl.com"
    fetchAssets(baseUrl);
    expect(fetchMock).toHaveBeenCalled();
    expect(fetchMock).toHaveBeenCalledWith(baseUrl);
}); 
});

The function fetchAssets call the fetch function with an specific url.

Now the function that mocks the fetch behavior:

export const assetsFetchMock = () => Promise.resolve({
ok: true,
status: 200,
json: async () => clientAssets
} as Response);

clientAssets is a object that I needed to return, you could replace it to the object or primitive you have to return.

4

you can also use this, which is similar to this answer https://stackoverflow.com/a/64819545/19334273

jest.spyOn(global, "fetch").mockImplementation( 
  jest.fn(
    () => Promise.resolve({ json: () => Promise.resolve({ data: 100 }), 
  }), 
) as jest.Mock ) 

from this comment Simple fetch mock using Typescript and Jest

mewc
  • 1,253
  • 1
  • 15
  • 24