node.jstypescriptunit-testingjestjsfetch-mock

How to setup fetch-mock with node-fetch, supertest and typescript


I am trying to add some jest tests to my node typescipt project. I would like to use supertest to call my koa router, but also use fetch-mock to mock requests that are made using node-fetch.

My solution so far is below, but the fetch in my router does not use the mocked fetch setup with fetch-mock. The unit test fails because the expected response I mocked is not returned. I have tried following the documentation for global fetch mocking but without success, and typescript makes it difficult to follow the non-typescript solutions I have found. I would like to avoid using the non-global sandbox if possible as I would have to re-write a lot of code to pass the fetch around.

server.spec.ts

import * as fetchMock from 'fetch-mock';
import * as request from 'supertest';
import server from './server';

afterEach(() => {
  server.close();
  fetchMock.reset();
});

describe('router', () => {
  test('GET: should return data', async () => {
    const expectedResponse = { test: 'TEST' };
    fetchMock.get('https://myapi.com/test', expectedResponse);

    const response = await request(server).get('/test');
    expect(response.status).toEqual(200);
    expect(response.body).toMatchObject(expectedResponse);
  });
});

server.ts

import * as Koa from 'koa';
import * as Router from 'koa-router';
import fetch from 'node-fetch';

const app = new Koa();

const router = new Router();

router.get('/test', async ctx => {
  const options = { method: 'GET' };
  try {
    const response = await fetch('https://myapi.com/test', options);
    ctx.body = await response.json();
  } catch (error) {
    error.fetchUrl = url;
    throw error;
  }
});

app.use(router.routes());

const server = app.listen(3000);

export default server;

Solution

  • You can mock node-fetch module manually by yourself. Here is the solution:

    server.ts:

    import Koa from 'koa';
    import Router from 'koa-router';
    import fetch from 'node-fetch';
    
    const app = new Koa();
    
    const router = new Router();
    
    router.get('/test', async ctx => {
      const options = { method: 'GET' };
      const url = 'https://myapi.com/test';
      try {
        const response = await fetch(url, options);
        ctx.body = await response.json();
      } catch (error) {
        error.fetchUrl = url;
        throw error;
      }
    });
    
    app.use(router.routes());
    
    function createHttpServer() {
      return app.listen(3000);
    }
    
    if (require.main === module) {
      createHttpServer();
    }
    
    export default createHttpServer;
    

    server.spec.ts:

    import request from 'supertest';
    import createHttpServer from './server';
    import fetch from 'node-fetch';
    
    const { Response } = jest.requireActual('node-fetch');
    const server = createHttpServer();
    
    jest.mock('node-fetch', () => jest.fn());
    
    afterAll(done => {
      server.close(done);
    });
    
    describe('router', () => {
      test('GET: should return data', async () => {
        const expectedResponse = { test: 'TEST' };
        (fetch as jest.MockedFunction<typeof fetch>).mockResolvedValueOnce(new Response(JSON.stringify(expectedResponse)));
    
        const response = await request(server).get('/test');
        expect(response.status).toEqual(200);
        expect(response.body).toEqual(expectedResponse);
      });
    
      test('GET: should throw error', async () => {
        const mockedFetchError = new Error('some error');
        (fetch as jest.MockedFunction<typeof fetch>).mockRejectedValueOnce(mockedFetchError);
        const response = await request(server).get('/test');
        expect(response.status).toEqual(500);
      });
    });
    

    Unit test result with coverage report:

     PASS  src/stackoverflow/56735795/server.spec.ts (8.487s)
      router
        ✓ GET: should return data (51ms)
        ✓ GET: should throw error (15ms)
    
      console.error node_modules/koa/lib/application.js:200
        undefined
    
      console.error node_modules/koa/lib/application.js:201
          Error: some error
              at Object.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/56735795/server.spec.ts:26:30)
              at step (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/56735795/server.spec.ts:32:23)
              at Object.next (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/56735795/server.spec.ts:13:53)
              at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/56735795/server.spec.ts:7:71
              at new Promise (<anonymous>)
              at Object.<anonymous>.__awaiter (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/56735795/server.spec.ts:3:12)
              at Object.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/56735795/server.spec.ts:25:35)
              at Object.asyncJestTest (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:102:37)
              at resolve (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
              at new Promise (<anonymous>)
              at mapper (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
              at promise.then (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
              at process._tickCallback (internal/process/next_tick.js:68:7)
    
      console.error node_modules/koa/lib/application.js:202
        undefined
    
    -----------|----------|----------|----------|----------|-------------------|
    File       |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    -----------|----------|----------|----------|----------|-------------------|
    All files  |    95.24 |       50 |      100 |    94.12 |                   |
     server.ts |    95.24 |       50 |      100 |    94.12 |                28 |
    -----------|----------|----------|----------|----------|-------------------|
    Test Suites: 1 passed, 1 total
    Tests:       2 passed, 2 total
    Snapshots:   0 total
    Time:        10.36s
    

    Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/56735795