nuxt / test-utils

🧪 Test utilities for Nuxt
http://nuxt.com/docs/getting-started/testing
MIT License
287 stars 74 forks source link

Overriding global $fetch with `ofetch.create` will break the test run #798

Open jrutila opened 3 months ago

jrutila commented 3 months ago

Environment

Nuxt project info:


@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
mathiasrando commented 3 months ago

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

jrutila commented 3 months ago

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.

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.