Search code examples
node.jsjestjsts-jest

How to correctly mock global fetch when using jest 29, nodejs 18 and ESM


I managed to successfully mock fetch but I had to silence typescript. Here is the minimum reproducible example:

import { jest } from '@jest/globals';

jest.spyOn(global, 'fetch');

const TEST_URL = 'http://aztec-node-url.com/';

const setFetchMock = (response: any): void => {
  // @ts-ignore
  global.fetch.mockResolvedValue({
    ok: true,
    json: () => response,
  });
};

export class HttpNode {
  private baseUrl: string;
  constructor(baseUrl: string) {
    this.baseUrl = baseUrl.toString().replace(/\/$/, '');
  }

  public async isReady(): Promise<boolean> {
    const url = new URL(this.baseUrl);
    const response = await fetch(url.toString());
    const respJson = await response.json();
    return respJson.isReady;
  }
}

describe('HttpNode', () => {
  let httpNode: HttpNode;

  beforeEach(() => {
    httpNode = new HttpNode(TEST_URL);
  });

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

  describe('isReady', () => {
    it.each([true, false])('should return %s when the node is ready', async () => {
      const response = { isReady: true };
      setFetchMock(response);

      const result = await httpNode.isReady();

      expect(fetch).toHaveBeenCalledWith(TEST_URL);
      expect(result).toBe(true);
    });
  });
});

But when I remove the // @ts-ignore line I get the following error:

enter image description here

How would I correctly type this?

This is the relevant part of package.json:

{
 ...
  "jest": {
    "preset": "ts-jest/presets/default-esm",
    "moduleNameMapper": {
      "^(\\.{1,2}/.*)\\.js$": "$1"
    },
    "testRegex": "./src/.*\\.test\\.ts$",
    "rootDir": "./src"
  },
  "dependencies": {
   ...
    "tslib": "^2.4.0"
  },
  "devDependencies": {
    "@jest/globals": "^29.5.0",
    "@types/jest": "^29.5.0",
    "@types/node": "^18.7.23",
    "jest": "^29.5.0",
    "ts-jest": "^29.1.0",
    "ts-node": "^10.9.1",
    "typescript": "^5.0.4"
  },
  ...
}

All the examples on the internet I could find seem to be outdated. Thank you


Solution

  • I managed to get around the issue. This is the relevant part:

    const setFetchMock = (response: any): void => {
      global.fetch = jest
        .fn<typeof global.fetch>()
        .mockImplementation((_input: RequestInfo | URL, _init?: RequestInit | undefined) => {
          return Promise.resolve({
            ok: true,
            json: () => response,
          } as Response);
        });
    };
    

    Here is the full test.