framework
framework copied to clipboard
support for unit testing in a nuxt environment
Describe the feature
Unit testing (e.g. [parts of ]composables or server functions) doesn't require a full built of nuxt.
Originally posted by @pi0 in https://github.com/vitest-dev/vitest/issues/2044#issuecomment-1251939691
We plan to support also unit testing with
nuxi testbut properly in a Nuxt environment. Exposing vite config only is not enough for doing that and in past similar approach made lots of headaches for testing in Nuxt 2. However, probably will expose as a@nuxt/kitutility anyway as an alternative to current usage that needs replicating vite config for aliases.
Related: https://github.com/nuxt/framework/issues/2465 (auto-import issues when testing components)
issue description from https://github.com/nuxt/framework/issues/7672 - @tobiasdiez
How do you not use auto-import functionality to import defineComponent? I can't find any module to import it from. 🤔
You can import it from vue or #imports - neither of which would work in a unit test out-of-the-box. You can see what they correspond to by inspecting the paths in .nuxt/tsconfig.json, and it would be possible to set up your jest environment with moduleNameMapper to respect these values. But I think we can do better.
I've managed to import the methods from #app, however mapping it to node_modules/@nuxt/bridge/dist/runtime/index as defined in ./.nuxt/tsconfig.json does not work for tests. They fail with a message saying that the path could not be resolved.
I've also tried adjusting the path by removing node_modules/, adding <rootDir> and turning it into a relative import with ./, none of the paths resolve when running tests.
There is also no #imports defined in .nuxt/tsconfig.json - perhaps the issue lies here?
Any idea why the paths resolve when running on the dev/production server but don't when running tests? Here is my jest.config.js for context:
module.exports = {
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/$1",
"^~/(.*)$": "<rootDir>/$1",
"^vue$": "vue/dist/vue.common.js",
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
"<rootDir>/__mocks__/fileMock.js",
"\\.(css|less|scss|sass)$": "identity-obj-proxy",
"^#app": "node_modules/@nuxt/bridge/dist/runtime/index",
},
watchman: true,
moduleFileExtensions: ["ts", "js", "vue", "json"],
transform: {
"^.+\\.ts?$": "ts-jest",
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest",
},
snapshotSerializers: ["<rootDir>/node_modules/jest-serializer-vue"],
collectCoverage: false,
collectCoverageFrom: [
"<rootDir>/components/**/*.vue",
"<rootDir>/pages/**/*.vue",
],
setupFiles: ["<rootDir>/tests/setup.ts"],
}
Hi @aseidma, this config has worked for me:
File jest.config.js:
module.exports = {
setupFiles: ['<rootDir>/jest.setup.js'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
'^~/(.*)$': '<rootDir>/$1',
'^vue$': 'vue/dist/vue.common.js',
'^utils/(.*)$': '<rootDir>/utils/$1',
'#app': '@nuxt/bridge/dist/runtime/index',
},
moduleFileExtensions: ['ts', 'js', 'vue', 'json', 'mjs'],
transform: {
'^.+\\.ts?$': 'ts-jest',
'^.+\\.ts$': 'ts-jest',
'^.+\\.js$': 'babel-jest',
'.*\\.(vue)$': 'vue-jest',
'.*\\.(mjs)$': 'babel-jest',
},
transformIgnorePatterns: [
'node_modules/(?!@nuxt)/',
],
collectCoverage: true,
collectCoverageFrom: [
'<rootDir>/components/**/*.vue',
'<rootDir>/pages/**/*.vue',
'<rootDir>/utils/**/*.ts',
],
testEnvironment: 'jsdom',
};
File jest.setup.js:
import Vue from 'vue';
import VueCompositionAPI from '@vue/composition-api';
Vue.use(VueCompositionAPI)
File babel.config.js (You will need to remove .babelrc file):
module.exports = {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": true
}
}
]
]
};
However, I am unable to test functions as useNuxtApp or useRuntimeConfig. The following error is displayed:
nuxt app instance unavailable
Thank you!
You can import it from
vueor#imports- neither of which would work in a unit test out-of-the-box. You can see what they correspond to by inspecting the paths in.nuxt/tsconfig.json, and it would be possible to set up your jest environment withmoduleNameMapperto respect these values. But I think we can do better.
I was able to map the #imports module via moduleNameMapper:
// jest.config.js
moduleNameMapper: {
"^@/(.*)": "<rootDir>/$1",
"#app": "<rootDir>/node_modules/nuxt3/dist/app/index.mjs",
"#imports": "<rootDir>/node_modules/nuxt3/dist/pages/runtime/composables.mjs",
},
But to make this work I need to include an import statement in my Vue file so jest has a reference to the module.
import { useRouter } from "#imports" // This needs to be used in my setup script
This works, but now I've lost the convenience of auto-importing if I want Jest to have a reference to the #imports module.
If anyone has any updates or progress on supporting auto-imports in a test env, I'm all ears.
What works for auto-imports is to add methods to global in setupfilesafterenv. For example,
global.useRuntimeConfig = () => constructConfig()
It would be nice if nuxt would provide helper utils to make this easier.
Just wanting to add that this is quite a crucial feature to have, because right now we can't run component tests in nuxt3.
Vitest also doesn't know about the magic auto-imports, so I think it would be nice if Nuxt3 exposed a method that we can call in vitest for this.
Edit: I've found that the unplugin-auto-import plugin works well with providing its own vite config to vitest.
Would like to add that, l also get the same problem with storybook on Nuxt 3
ReferenceError: ref is not defined
It also feels like this auto import feature breaks the benefit of composition api to see where imports are from. (Just thinking out loud)
For vitest, using unplugin-auto-import helps mapped auto-imported dirs such as composables and components. However, for nuxt's built in global composables such as useState etc, still can't figure it out.
I found a clue that you could set similar config to what you'd set in jest using moduleNameMapper, with resolve.alias. But so far I tried mimicking the above jest config to no avail.
@TheDutchCoder would you share your config here, if you could get it to work? Thx
For vitest, using
unplugin-auto-importhelps mapped auto-imported dirs such ascomposablesandcomponents. However, for nuxt's built in global composables such asuseStateetc, still can't figure it out.I found a clue that you could set similar config to what you'd set in jest using
moduleNameMapper, withresolve.alias. But so far I tried mimicking the above jest config to no avail.@TheDutchCoder would you share your config here, if you could get it to work? Thx
Any update for this issue?
For vitest, using
unplugin-auto-importhelps mapped auto-imported dirs such ascomposablesandcomponents. However, for nuxt's built in global composables such asuseStateetc, still can't figure it out. ...
I have a similar issue trying to test useStorage. Here is a reproduction. storage.test.ts includes what I'm trying to do, the other tests are attempts to find a workaround but all tests fail.
Am I correct in saying that Jest testing isn't supported yet in Nuxt 3?
The installation document seems a little thin on the ground. https://v3.nuxtjs.org/getting-started/testing
There are both jest + vitest drivers for Nuxt e2e testing.
I followed the guide, but I get the error SyntaxError: Cannot use import statement outside a module when running a test. This suggests I will need to setup Babel, but the guide does not mention this.
The test spec is in the directory /tests.
Ah, yes. Jest does struggle with ESM. That's not an issue with Nuxt, per se, but it's a tricky one. There are various guides online.