Overriding global $fetch with `ofetch.create` will break the test run
Environment
Nuxt project info:
- Operating System: Linux
- Node Version: v18.18.0
- Nuxt Version: 3.11.1
- CLI Version: 3.11.0
- Nitro Version: 2.9.4
- Package Manager: [email protected]
- Builder: -
- User Config: devtools
- Runtime Modules: -
- Build Modules: -
@nuxt/test-utils version: 3.12.0 vitest version: 1.4.0
Reproduction
Here is a reproduce stackblitz: https://stackblitz.com/edit/github-lxyzpn?file=plugins%2Fonrequest.client.ts
Create a plugin that will override the default $fetch. This is handy when trying to inject your own headers into the HTTP requests. If you google this, you will find a helpful SO solution: https://stackoverflow.com/a/75871291/216846
import { ofetch } from 'ofetch';
export default defineNuxtPlugin((_nuxtApp) => {
globalThis.$fetch = ofetch.create({ // <- this is the important part!
onRequest({ request, options }) {
options.headers = { Authorization: `Bearer token` };
console.log('onRequest', request);
},
onRequestError({ error }) {
console.log(error);
},
});
});
This works fine when running the dev environment or prod environment. When you now run @nuxt/test-utils based test case, the tests pass, but the run fails to this error (see the whole log in logs):
TypeError: Cannot set property request of FetchError: [GET] "/_nuxt/builds/meta/test.json": <no response> Failed to parse URL from /_nuxt/builds/meta/test.json which has only a getter
Describe the bug
The correct way to implement this kind of plugin that edits the HTTP requests is described here: https://nuxt.com/docs/examples/advanced/use-custom-fetch-composable. Instead of returning you just inject the globalThis.$fetch with the created fetch instance.
The bug is that in dev and prod environments you can as well do it like this globalThis.$fetch = ofetch.create. Everything works, except when you run unit tests with @nuxt/test-utils, you get errors described above.
So, again, the correct way is to do globalThis.$fetch = $fetch.create but I think there should be a warning or something guiding the devs to do it correctly. Or at least they find this bug issue and can fix it in their code.
Additional context
No response
Logs
~/projects/vbwirlala.github 4m 11s
❯ npm run test
> test
> vitest
DEV v1.4.0 /home/projects/vbwirlala.github
stdout | Object.onRequest (/home/projects/vbwirlala.github/plugins/onrequest.client.ts:8:15)
onRequest /_nuxt/builds/meta/test.json
onRequest /_nuxt/builds/meta/test.json
stdout | Object.onRequestError (/home/projects/vbwirlala.github/plugins/onrequest.client.ts:11:15)
TypeError: Failed to parse URL from /_nuxt/builds/meta/test.json
at _0x88e3f9 (https://vbwirlalagithub-bvqi.w-credentialless-staticblitz.com/blitz.810981ba.js:352:614955)
at _0x303d3a._0x5ec96b (https://vbwirlalagithub-bvqi.w-credentialless-staticblitz.com/blitz.810981ba.js:352:625681)
at Object.t.exports.fetch (node:internal/deps/undici/undici:194:308109)
at $fetchRaw2 (/home/projects/vbwirlala.github/node_modules/ofetch/dist/shared/ofetch.00501375.mjs:236:26)
at $fetch2 (/home/projects/vbwirlala.github/node_modules/ofetch/dist/shared/ofetch.00501375.mjs:278:15) {
[cause]: TypeError [ERR_INVALID_URL]: Invalid URL
at __node_internal_ (node:internal/errors:36:5406)
at new <anonymous> (node:internal/errors:36:4168)
at new URL (node:internal/url:48:8018)
at new URL (/home/projects/vbwirlala.github/node_modules/happy-dom/lib/url/URL.js:25:1)
at new oe (node:internal/deps/undici/undici:194:83653)
at fetch (node:internal/deps/undici/undici:194:307099)
at Object.t.exports.fetch (node:internal/deps/undici/undici:194:308078)
at node:internal/process/pre_execution:56:2507
at $fetchRaw2 (/home/projects/vbwirlala.github/node_modules/ofetch/dist/shared/ofetch.00501375.mjs:236:32)
at $fetch2 (/home/projects/vbwirlala.github/node_modules/ofetch/dist/shared/ofetch.00501375.mjs:278:15) {
input: '/_nuxt/builds/meta/test.json',
code: 'ERR_INVALID_URL'
}
}
TypeError: Failed to parse URL from /_nuxt/builds/meta/test.json
at _0x88e3f9 (https://vbwirlalagithub-bvqi.w-credentialless-staticblitz.com/blitz.810981ba.js:352:614955)
at _0x303d3a._0x5ec96b (https://vbwirlalagithub-bvqi.w-credentialless-staticblitz.com/blitz.810981ba.js:352:625681)
at Object.t.exports.fetch (node:internal/deps/undici/undici:194:308109)
at $fetchRaw2 (/home/projects/vbwirlala.github/node_modules/ofetch/dist/shared/ofetch.00501375.mjs:236:26)
at $fetchRaw2 (/home/projects/vbwirlala.github/node_modules/ofetch/dist/shared/ofetch.00501375.mjs:245:14)
at $fetch2 (/home/projects/vbwirlala.github/node_modules/ofetch/dist/shared/ofetch.00501375.mjs:278:15) {
[cause]: TypeError [ERR_INVALID_URL]: Invalid URL
at __node_internal_ (node:internal/errors:36:5406)
at new <anonymous> (node:internal/errors:36:4168)
at new URL (node:internal/url:48:8018)
at new URL (/home/projects/vbwirlala.github/node_modules/happy-dom/lib/url/URL.js:25:1)
at new oe (node:internal/deps/undici/undici:194:83653)
at fetch (node:internal/deps/undici/undici:194:307099)
at Object.t.exports.fetch (node:internal/deps/undici/undici:194:308078)
at node:internal/process/pre_execution:56:2507
at $fetchRaw2 (/home/projects/vbwirlala.github/node_modules/ofetch/dist/shared/ofetch.00501375.mjs:236:32) {
input: '/_nuxt/builds/meta/test.json',
code: 'ERR_INVALID_URL'
}
}
stdout | createSuspenseBoundary (/home/projects/vbwirlala.github/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:1442:43)
<Suspense> is an experimental feature and its API will likely change.
✓ test/my.spec.ts (1)
✓ abba (1)
✓ should be abba
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Errors ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Vitest caught 1 unhandled error during the test run.
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Uncaught Exception ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
TypeError: Cannot set property request of FetchError: [GET] "/_nuxt/builds/meta/test.json": <no response> Failed to parse URL from /_nuxt/builds/meta/test.json which has only a getter
❯ _0x46d1e6 ../../../blitz.810981ba.js:352:617597
❯ _0x1eee56 ../../../blitz.810981ba.js:352:615189
❯ FetchError.get ../../../blitz.810981ba.js:352:615388
❯ Module.processError node_modules/@vitest/utils/dist/error.js:95:11
❯ catchError node_modules/vitest/dist/vendor/execute.2_yoIC01.js:396:39
❯ process.unhandledRejection node_modules/vitest/dist/vendor/execute.2_yoIC01.js:406:37
❯ EventEmitter.emit node:events:42:9202
❯ emit node:internal/process/promises:230:1176
❯ processPromiseRejections node:internal/process/promises:230:3782
❯ processTicksAndRejections node:internal/process/task_queues:107:1089
This error originated in "test/my.spec.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Test Files 1 passed (1)
Tests 1 passed (1)
Errors 1 error
Start at 08:55:23
Duration 4.31s (transform 467ms, setup 535ms, collect 11ms, tests 2ms, environment 800ms, prepare 1.37s)
FAIL Tests failed. Watching for file changes...
press h to show help, press q to quit
If you don't actually need the plugin for the test I would assume you could add an early return in your defineNuxtPlugin based on the environment. Maybe you can useimport.meta.test or pass a variable yourself and check for that using import.meta.env.YOUR_VARIABLE and your script in package.json being YOUR_VARIABLE=true vitest.
https://nuxt.com/docs/api/advanced/import-meta
If you don't actually need the plugin for the test I would assume you could add an early return in your
defineNuxtPluginbased on the environment.
This is a good tip if you don't need the plugin. Still, cluttering the plugin code with that kind of a check is also little smelly. I investigated and I don't think there is a way to disable, say in vitest.config, certain autoloaded plugins in tests run by test-utils? That is totally different topic, though.