Skip to content

Instantly share code, notes, and snippets.

@nickcampbell18
Created October 9, 2023 14:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nickcampbell18/5ed0539929fb8dbc8a12c974c7b00e1f to your computer and use it in GitHub Desktop.
Save nickcampbell18/5ed0539929fb8dbc8a12c974c7b00e1f to your computer and use it in GitHub Desktop.
Replacing the Undici global dispatch logic with a symbol-less Jest-compatible implementation
test("mocking a request", async () => {
const mockAgent = new MockAgent()
;(global as any).setMockedFetchGlobalDispatcher(mockAgent)
await fetch(...)
})
const { TestEnvironment } = require("jest-environment-node")
const { fetch: undiciFetch, MockAgent } = require("undici")
const v8 = require('node:v8');
module.exports = class extends TestEnvironment {
constructor(config, context) {
super(config, context)
this.mockGlobalFetch()
}
/**
* Replace the global fetch implementation with one which still uses undici, but pulls
* in the correct "dispatcher" from the global object.
*
* Background:
* 1. Every call to fetch uses a "dispatcher", i.e. the underlying HTTP client
* 2. If you don't explicitly specify it when calling `fetch(...)`, Undici / Node
* internally uses a special symbol to find the default global one inside the
* global namespace [1]
* 3. Jest runs each test in a separate process, so Symbol.for(...) returns a
* different value in the test process.
* 4. This means that calling `setGlobalDispatcher(...)` in the test has no effect on
* the global version that fetch otherwise would use.
* 5. This means that your mocks won't apply and won't be cleared up automatically.
*
* This method overrides the global fetch, to specify our own global dispatcher, and
* creates a special method that can be used to replace the dispatcher.
*
* [1]: https://github.com/nodejs/undici/blob/e5c9d703e63cd5ad691b8ce26e3f9a81c598f2e3/lib/global.js#L5
*/
mockGlobalFetch() {
this.global.fetch = (input, init) => undiciFetch(input, {
dispatcher: this.global["mockAgent"],
...init,
})
this.global.setMockedFetchGlobalDispatcher = (agent) =>
Object.defineProperty(this.global, "mockAgent", {
value: agent,
writable: true,
enumerable: false,
configurable: false
})
}
}
@GziAzman
Copy link

GziAzman commented Dec 5, 2023

@nickcampbell18 This saved my day, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment