How do I mock Node.js fetch HTTP requests/responses in Node 18?

5k Views Asked by At

I am using the new (as of version 18) Node.js "fetch" API to perform HTTP requests e.g.

const response = await fetch(SOMEURL)
const json = await response.json()

This works, but I want to "mock" those HTTP requests so that I can do some automated testing and be able to simulate some HTTP responses to see if my code works correctly.

Normally I have used the excellent nock package alongside Axios to mock HTTP requests, but it doesn't appear to work with fetch in Node 18.

So how can I mock HTTP requests and responses when using fetch in Node.js?

3

There are 3 best solutions below

0
Glynn Bird On

Node 18's fetch function isn't built on the Node.js http module like most HTTP libraries (including Axios, request etc) - it's a total rewrite of an HTTP client built on the lower-level "net" library called undici. As such, "nock" cannot intercept requests made from the fetch function (I believe the team are looking to fix this, but at the time of writing, Nock 13.2.9, Nock does not work for fetch requests).

The solution is to use a MockAgent that is built into the undici package.

Let's say your code looks like this:

// constants
const U = 'http://127.0.0.1:5984'
const DB = 'users'

const main = async () => {

  // perform request
  const r = await fetch(U + '/' + DB)

  // parse response as JSON
  const j = await r.json()
  console.log(j)
}

main()

This code makes a real HTTP request to a CouchDB server running on localhost.

To mock this request, we need to add undici into our project:

npm install --save-dev undici

and add some code to intercept the request:

// constants
const U = 'http://127.0.0.1:5984'
const DB = 'users'

// set up mocking of HTTP requests
const { MockAgent, setGlobalDispatcher } = require('undici')
const mockAgent = new MockAgent()
const mockPool = mockAgent.get(U)
setGlobalDispatcher(mockAgent)

const main = async () => {
  // intercept GET /users requests
  mockPool.intercept({ path: '/' + DB }).reply(200, { ok: true })

  // perform request
  const r = await fetch(U + '/' + DB,)

  // parse response as JSON
  const j = await r.json()
  console.log(j)
}

main()

The above code now has its HTTP "fetch" request intercepted with mocked response.

2
jmillman On

I am unable to get the mocking to work. I am not sure the sample above is doing what it is intended to do. I think it's actually making a call to localhost. I haven't been able to use MockAgent to mock the fetch call. I keep getting the result of the request (which is a google error page in this example). It should be equivalent as the code above, but I changed the url. When I had localhost I got fetch errors (since it wasn't being intercepted).

const { MockAgent, setGlobalDispatcher, } = require('undici');
test('test', async ()=>{
  // constants
  const U = 'http://www.google.com'
  const DB = 'users'

  const mockAgent = new MockAgent()
  const mockPool = mockAgent.get(U)
  setGlobalDispatcher(mockAgent)

    // intercept GET /users requests
  mockPool.intercept({ path: '/' + DB }).reply(200, { ok: true })
  const opts = { method: 'GET' };
  // perform request
  const r = await fetch(U + '/' + DB,opts)
  // parse response as JSON
  const j = await r.text()
  console.log(j)
});
1
jotafeldmann On

Using Node 18.15.0 I was able to do the following without any third-party lib:

import assert from 'node:assert'
import { describe, it, mock } from 'node:test'


describe('Class', () => {
    it('fetch stuff', async () => {
        const json = () => { 
            return {
                key: 'value'
            }
        }
        mock.method(global, 'fetch', () => {
            return { json, status: 200 }
        })

        const response = await fetch()
        assert.strictEqual(response.status, 200)

        const responseJson = await response.json()
        assert.strictEqual(responseJson.key, 'value')

        mock.reset()
    })
})