nuxt-vitest
A vitest environment for testing code that needs a Nuxt runtime environment
Warning
This library is in active development and you should pin the patch version before using.
Installation
- First install
nuxt-vitest
:
pnpm add -D nuxt-vitest vitest happy-dom
yarn add --dev nuxt-vitest vitest happy-dom
npm i -D nuxt-vitest vitest happy-dom
- Add
nuxt-vitest
to your nuxt.config.js
:
export default defineNuxtConfig({
modules: [
'nuxt-vitest'
]
})
- Then create a
vitest.config.ts
with the following content:
import { defineVitestConfig } from 'nuxt-vitest/config'
export default defineVitestConfig({
})
- Setting environment for your tests
By default, nuxt-vitest
will not change your default Vitest environment, so you can do fine-grain opt-in and run Nuxt tests together with other unit tests.
We provided a filename convention that test files contains .nuxt.
, like *.nuxt.test.{js,ts}
and *.nuxt.spec.{js,ts}
, will be run in Nuxt environment automatically.
Or you can add @vitest-environment nuxt
in your test file as a comment to opt-in per test file.
import { test } from 'vitest'
test('my test', () => {
})
Finally, you can set environment: 'nuxt'
, to enable Nuxt environment for all tests.
import { fileURLToPath } from 'node:url'
import { defineVitestConfig } from 'nuxt-vitest/config'
export default defineVitestConfig({
test: {
environment: 'nuxt',
}
})
If you have set environment: 'nuxt'
by default, you can then opt-out of the default environment per test file as needed.
import { test } from 'vitest'
test('my test', () => {
})
👉 Important notes
When you run your tests within the Nuxt environment, they will be running in a happy-dom
environment. Before your tests run, a global Nuxt app will be initialised (including, for example, running any plugins or code you've defined in your app.vue
).
This means you should take particular care not to mutate the global state in your tests (or, if you have, to reset it afterwards).
🎭 Built-in mocks
nuxt-vitest
provides some built-in mocks for the DOM environment, both for happy-dom
and jsdom
.
intersectionObserver
Default true
, creates a dummy class without any functionality for the IntersectionObserver API
indexedDB
Default false
, uses fake-indexeddb
to create a functional mock of the IndexedDB API
These can be configured in the environmentOptions
section of your vitest.config.mjs
file:
export default defineVitestConfig({
test: {
environmentOptions: {
nuxt: {
mock: {
intersectionObserver: true,
indexedDb: true,
}
}
}
}
})
🛠️ Helpers
nuxt-vitest
provides a number of helpers to make testing Nuxt apps easier.
mountSuspended
mountSuspended
allows you to mount any vue component within the Nuxt environment, allowing async setup and access to injections from your Nuxt plugins. For example:
it('can mount some component', async () => {
const component = await mountSuspended(SomeComponent)
expect(component.text()).toMatchInlineSnapshot(
'This is an auto-imported component'
)
})
it('can also mount an app', async () => {
const component = await mountSuspended(App, { route: '/test' })
expect(component.html()).toMatchInlineSnapshot(`
"<div>This is an auto-imported component</div>
<div> I am a global component </div>
<div>/</div>
<a href=\\"/test\\"> Test link </a>"
`)
})
renderSuspended
renderSuspended
allows you to render any vue component within the Nuxt environment using @testing-library/vue
, allowing async setup and access to injections from your Nuxt plugins.
This should be used together with utilities from testing-library, e.g. screen
and fireEvent
. Install @testing-library/vue in your project to use these.
Additionally testing-library also relies on testing globals for cleanup. You should turn these on in your Vitest config.
The passed in component will be rendered inside a <div id="test-wrapper"></div>
.
Examples:
import { renderSuspended } from 'nuxt-vitest/utils'
import { screen } from '@testing-library/vue'
it('can render some component', async () => {
await renderSuspended(SomeComponent)
expect(screen.getByText('This is an auto-imported component')).toBeDefined()
})
import { renderSuspended } from 'nuxt-vitest/utils'
it('can also render an app', async () => {
const html = await renderSuspended(App, { route: '/test' })
expect(html()).toMatchInlineSnapshot(`
"<div id=\\"test-wrapper\\">
<div>This is an auto-imported component</div>
<div> I am a global component </div>
<div>Index page</div><a href=\\"/test\\"> Test link </a>
</div>"
`)
})
mockNuxtImport
mockNuxtImport
allows you to mock Nuxt's auto import functionality. For example, to mock useStorage
, you can do so like this:
import { mockNuxtImport } from 'nuxt-vitest/utils'
mockNuxtImport('useStorage', () => {
return () => {
return { value: 'mocked storage' }
}
})
Note: mockNuxtImport
can only be used once per mocked import per test file. It is actually a macro that gets transformed to vi.mock
and vi.mock
is hoisted, as described here.
If you need to mock a Nuxt import and provide different implementations between tests, you can do it by creating and exposing your mocks using vi.hoisted
, and then use those mocks in mockNuxtImport
. You then have access to the mocked imports, and can change the implementation between tests. Be careful to restore mocks before or after each test to undo mock state changes between runs.
import { vi } from 'vitest'
import { mockNuxtImport } from 'nuxt-vitest/utils'
const { useStorageMock } = vi.hoisted(() => {
return {
useStorageMock: vi.fn().mockImplementation(() => {
return { value: 'mocked storage'}
})
}
})
mockNuxtImport('useStorage', () => {
return useStorageMock
})
useStorageMock.mockImplementation(() => {
return { value: 'something else' }
})
mockComponent
mockComponent
allows you to mock Nuxt's component.
The first argument can be the component name in PascalCase, or the relative path of the component.
The second argument is a factory function that returns the mocked component.
For example, to mock MyComponent
, you can:
import { mockComponent } from 'nuxt-vitest/utils'
mockComponent('MyComponent', {
props: {
value: String
},
setup(props) {
}
})
mockComponent('~/components/my-component.vue', async () => {
return {
setup(props) {
}
}
})
mockComponent('MyComponent', () => import('./MockComponent.vue'))
Note: You can't reference to local variables in the factory function since they are hoisted. If you need to access Vue APIs or other variables, you need to import them in your factory function.
mockComponent('MyComponent', async () => {
const { ref, h } = await import('vue')
return {
setup(props) {
const counter = ref(0)
return () => h('div', null, counter.value)
}
}
})
registerEndpoint
registerEndpoint
allows you create Nitro endpoint that returns mocked data. It can come in handy if you want to test a component that makes requests to API to display some data.
The first argument is the endpoint name (e.g. /test/
).
The second argument is a factory function that returns the mocked data.
For example, to mock /test/
endpoint, you can do:
import { registerEndpoint } from 'nuxt-vitest/utils'
registerEndpoint("/test/", () => ({
test: "test-field"
}))
By default, your request will be made using the GET
method. You may use another method by setting an object as the second argument instead of a function.
import { registerEndpoint } from 'nuxt-vitest/utils'
registerEndpoint("/test/", {
method: "POST",
handler: () => ({ test: "test-field" })
})
Note: If your requests in a component go to external API, you can use baseURL
and then make it empty using Nuxt Enviroment Config ($test
) so all your requests will go to Nitro server.
Conflict with @nuxt/test-utils
nuxt-vitest
and @nuxt/test-utils
need to run in different testing environments and so can't be used in the same file.
If you would like to use @nuxt/test-utils
to conduct end-to-end tests on your Nuxt app, you can split your tests into separate files. You then either specify a test environment per-file with the special // @vitest-environment nuxt
comment, or name your nuxt-vitest
files with the .nuxt.spec.ts
extension.
app.nuxt.spec.js
import { mockNuxtImport } from "nuxt-vitest/utils";
mockNuxtImport('useStorage', () => {
return () => {
return { value: 'mocked storage' }
}
})
app.e2e.spec.js
import { setup, $fetch } from '@nuxt/test-utils';
await setup({
setupTimeout: 10000,
});
💻 Development
- Clone this repository
- Enable Corepack using
corepack enable
(use npm i -g corepack
for Node.js < 16.10) - Install dependencies using
pnpm install
- Stub the library using
pnpm dev:prepare
- Run interactive tests using
pnpm test
License
Made with ❤️
Published under the MIT License.