docs: add docs for runtime test environment (#24658)

This commit is contained in:
Daniel Roe 2023-12-19 14:33:53 +01:00 committed by GitHub
parent 1b93e604d3
commit 3de719c8ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -5,36 +5,376 @@ navigation.icon: i-ph-check-circle-duotone
---
::callout
Test utils are still in development and the API and behavior may change. Currently, it is in preview stage but not yet ready for testing production apps.
If you are a module author, you can find more specific information in the [Module Author's guide](/docs/guide/going-further/modules#testing)
If you are a module author, you can find more specific information in the [Module Author's guide](/docs/guide/going-further/modules#testing).
::
In Nuxt 3, we have a rewritten version of `@nuxt/test-utils`. We support [Vitest](https://github.com/vitest-dev/vitest) and [Jest](https://jestjs.io) as test runners.
Nuxt offers first-class support for end-to-end and unit testing of your Nuxt application via `@nuxt/test-utils`, a library of test utilities and configuration that currently powers the [tests we use on Nuxt itself](https://github.com/nuxt/nuxt/tree/main/test) and tests throughout the module ecosystem.
## Installation
In order to allow you to manage your other testing dependencies, `@nuxt/test-utils` ships with various optional peer dependencies. For example:
- you can choose between `happy-dom` and `jsdom` for a runtime Nuxt environment
- you can choose between `vitest` and `jest` for end-to-end test runners
- `playwright-core` is only required if you wish to use the built-in browser testing utilities
::code-group
```bash [yarn]
yarn add --dev @nuxt/test-utils vitest
yarn add --dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
```
```bash [npm]
npm i --save-dev @nuxt/test-utils vitest
npm i --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
```
```bash [pnpm]
pnpm add -D @nuxt/test-utils vitest
pnpm add -D @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
```
```bash [bun]
bun add --dev @nuxt/test-utils vitest
bun add --dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
```
::
## Setup
## Unit Testing
In each `describe` block where you are taking advantage of the `@nuxt/test-utils` helper methods, you will need to set up the test context before beginning.
We currently ship an environment for unit testing code that needs a [Nuxt](https://nuxt.com) runtime environment. It currently _only has support for `vitest`_ (although contribution to add other runtimes would be welcome).
### Setup
1. Add `@nuxt/test-utils/module` to your `nuxt.config` file (optional). It adds a Vitest integration to your Nuxt DevTools which supports running your unit tests in development.
```js
export default defineNuxtConfig({
modules: [
'@nuxt/test-utils/module'
]
})
```
2. Create a `vitest.config.ts` with the following content:
```ts
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
// any custom Vitest config you require
})
```
### Using a Nuxt Runtime Environment
By default, `@nuxt/test-utils` will not change your default Vitest environment, so you can do fine-grained opt-in and run Nuxt tests together with other unit tests.
You can opt in to a Nuxt environment by adding `.nuxt.` to the test file's name (for example, `my-file.nuxt.test.ts` or `my-file.nuxt.spec.ts`) or by adding `@vitest-environment nuxt` as a comment directly in the test file.
```js
// @vitest-environment nuxt
import { test } from 'vitest'
test('my test', () => {
// ... test with Nuxt environment!
})
```
You can alternatively set `environment: 'nuxt'` in your Vitest configuration to enable the Nuxt environment for **all tests**.
```js
// vitest.config.ts
import { fileURLToPath } from 'node:url'
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
test: {
environment: 'nuxt',
// you can optionally set Nuxt-specific environment options
// environmentOptions: {
// nuxt: {
// rootDir: fileURLToPath(new URL('./playground', import.meta.url)),
// overrides: {
// // other Nuxt config you want to pass
// }
// }
// }
}
})
```
If you have set `environment: 'nuxt'` by default, you can then opt _out_ of the [default environment](https://vitest.dev/guide/environment.html#test-environment) per test file as needed.
```js
// @vitest-environment node
import { test } from 'vitest'
test('my test', () => {
// ... test without Nuxt environment!
})
```
::callout{icon="i-ph-warning-duotone" color="amber"}
When you run your tests within the Nuxt environment, they will be running in a [`happy-dom`](https://github.com/capricorn86/happy-dom) or [`jsdom`](https://github.com/jsdom/jsdom) environment. Before your tests run, a global Nuxt app will be initialized (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 need to, to reset it afterwards).
::
### 🎭 Built-In Mocks
`@nuxt/test-utils` provides some built-in mocks for the DOM environment.
#### `intersectionObserver`
Default `true`, creates a dummy class without any functionality for the IntersectionObserver API
#### `indexedDB`
Default `false`, uses [`fake-indexeddb`](https://github.com/dumbmatter/fakeIndexedDB) to create a functional mock of the IndexedDB API
These can be configured in the `environmentOptions` section of your `vitest.config.ts` file:
```js
export default defineVitestConfig({
test: {
environmentOptions: {
nuxt: {
mock: {
intersectionObserver: true,
indexedDb: true,
}
}
}
}
})
````
### 🛠️ Helpers
`@nuxt/test-utils` 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:
```ts
// tests/components/SomeComponents.nuxt.spec.ts
it('can mount some component', async () => {
const component = await mountSuspended(SomeComponent)
expect(component.text()).toMatchInlineSnapshot(
'This is an auto-imported component'
)
})
// tests/App.nuxt.spec.ts
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](https://testing-library.com/docs/vue-testing-library/intro) 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](https://vitest.dev/config/#globals).
The passed in component will be rendered inside a `<div id="test-wrapper"></div>`.
Examples:
```ts
// tests/components/SomeComponents.nuxt.spec.ts
import { renderSuspended } from '@nuxt/test-utils/runtime'
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()
})
// tests/App.nuxt.spec.ts
import { renderSuspended } from '@nuxt/test-utils/runtime'
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:
```ts
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport('useStorage', () => {
return () => {
return { value: 'mocked storage' }
}
})
// your tests here
```
> **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](https://vitest.dev/api/vi.html#vi-mock).
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`](https://vitest.dev/api/vi.html#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](https://vitest.dev/api/mock.html#mockrestore) before or after each test to undo mock state changes between runs.
```ts
import { vi } from 'vitest'
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
const { useStorageMock } = vi.hoisted(() => {
return {
useStorageMock: vi.fn().mockImplementation(() => {
return { value: 'mocked storage'}
})
}
})
mockNuxtImport('useStorage', () => {
return useStorageMock
})
// Then, inside a test
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:
```ts
import { mockComponent } from '@nuxt/test-utils/runtime'
mockComponent('MyComponent', {
props: {
value: String
},
setup(props) {
// ...
}
})
// relative path or alias also works
mockComponent('~/components/my-component.vue', async () => {
// or a factory function
return {
setup(props) {
// ...
}
}
})
// or you can use SFC for redirecting to a mock component
mockComponent('MyComponent', () => import('./MockComponent.vue'))
// your tests here
```
> **Note**: You can't reference 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.
```ts
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:
```ts
import { registerEndpoint } from '@nuxt/test-utils/runtime'
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.
```ts
import { registerEndpoint } from '@nuxt/test-utils/runtime'
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 Environment Config (`$test`) so all your requests will go to Nitro server.
#### Conflict with End-To-End Testing
`@nuxt/test-utils/runtime` and `@nuxt/test-utils/e2e` need to run in different testing environments and so can't be used in the same file.
If you would like to use both the end-to-end and unit testing functionality of `@nuxt/test-utils`, 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 runtime unit test files with the `.nuxt.spec.ts` extension.
`app.nuxt.spec.ts`
```ts
import { mockNuxtImport } from "@nuxt/test-utils/runtime"
mockNuxtImport('useStorage', () => {
return () => {
return { value: 'mocked storage' }
}
})
```
`app.e2e.spec.ts`
```ts
import { setup, $fetch } from '@nuxt/test-utils/e2e'
await setup({
setupTimeout: 10000,
})
// ...
```
## End-To-End Testing
For end-to-end testing, we support [Vitest](https://github.com/vitest-dev/vitest) and [Jest](https://jestjs.io) as test runners.
### Setup
In each `describe` block where you are taking advantage of the `@nuxt/test-utils/e2e` helper methods, you will need to set up the test context before beginning.
```ts [test/my-test.spec.ts]
import { describe, test } from 'vitest'
import { setup, $fetch } from '@nuxt/test-utils'
import { setup, $fetch } from '@nuxt/test-utils/e2e'
describe('My test', async () => {
await setup({
@ -51,7 +391,7 @@ Behind the scenes, `setup` performs a number of tasks in `beforeAll`, `beforeEac
Please use the options below for the `setup` method.
### Nuxt Config
#### Nuxt Config
- `rootDir`: Path to a directory with a Nuxt app to be put under test.
- Type: `string`
@ -65,13 +405,13 @@ Please use the options below for the `setup` method.
- Type: `NuxtConfig`
- Default: `{}` -->
### Timings
#### Timings
- `setupTimeout`: The amount of time (in milliseconds) to allow for `setupTest` to complete its work (which could include building or generating files for a Nuxt application, depending on the options that are passed).
- Type: `number`
- Default: `60000`
### Features
#### Features
- `server`: Whether to launch a server to respond to requests in the test suite.
- Type: `boolean`
@ -95,42 +435,51 @@ Please use the options below for the `setup` method.
- Type: `'vitest' | 'jest'`
- Default: `'vitest'`
## APIs
### APIs
### `$fetch(url)`
#### `$fetch(url)`
Get the HTML of a server-rendered page.
```ts
import { $fetch } from '@nuxt/test-utils'
import { $fetch } from '@nuxt/test-utils/e2e'
const html = await $fetch('/')
```
### `fetch(url)`
#### `fetch(url)`
Get the response of a server-rendered page.
```ts
import { fetch } from '@nuxt/test-utils'
import { fetch } from '@nuxt/test-utils/e2e'
const res = await fetch('/')
const { body, headers } = res
```
### `url(path)`
#### `url(path)`
Get the full URL for a given page (including the port the test server is running on.)
```ts
import { url } from '@nuxt/test-utils'
import { url } from '@nuxt/test-utils/e2e'
const pageUrl = url('/page')
// 'http://localhost:6840/page'
```
## Testing in a Browser
### Testing in a Browser
::callout
We are working on it, stay tuned!
::
We provide built-in support using Playwright within `@nuxt/test-utils`, but you can also use other test runners for end-to-end browser testing.
#### `createPage(url)`
You can create a configured Playwright browser instance, and (optionally) point it at a path from the running server. You can find out more about the API methods available from [in the Playwright documentation](https://playwright.dev/docs/api/class-page).
```ts
import { createPage } from '@nuxt/test-utils/e2e'
const page = await createPage('/page')
// you can access all the Playwright APIs from the `page` variable
```