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 and tests throughout the module ecosystem.
In order to allow you to manage your other testing dependencies, @nuxt/test-utils ships with various optional peer dependencies. For example:
happy-dom and jsdom for a runtime Nuxt environmentvitest, cucumber, jest and playwright for end-to-end test runnersplaywright-core is only required if you wish to use the built-in browser testing utilities (and are not using @playwright/test as your test runner)npm i --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
yarn add --dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
pnpm add -D @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
bun add --dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
We currently ship an environment for unit testing code that needs a Nuxt runtime environment. It currently only has support for vitest (although contribution to add other runtimes would be welcome).
@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.export default defineNuxtConfig({
  modules: [
    '@nuxt/test-utils/module',
  ],
})
vitest.config.ts with the following content:import { defineConfig } from 'vitest/config'
import { defineVitestProject } from '@nuxt/test-utils/config'
export default defineConfig({
  test: {
    projects: [
      {
        test: {
          name: 'unit',
          include: ['test/{e2e,unit}/*.{test,spec}.ts'],
          environment: 'node',
        },
      },
      await defineVitestProject({
        test: {
          name: 'nuxt',
          include: ['test/nuxt/*.{test,spec}.ts'],
          environment: 'nuxt',
        },
      }),
    ],
  },
})
@nuxt/test-utils in your vitest config, It is necessary to have "type": "module" specified in your package.json or rename your vitest config file appropriately.i.e.,
vitest.config.m{ts,js}.
.env.test file.Using Vitest projects, you have fine-grained control over which tests run in which environment:
test/unit/ - these run in a Node environment for speedtest/nuxt/ - these will run within a Nuxt runtime environmentIf you prefer a simpler setup and want all tests to run in the Nuxt environment, you can use the basic configuration:
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)),
    //     domEnvironment: 'happy-dom', // 'happy-dom' (default) or 'jsdom'
    //     overrides: {
    //       // other Nuxt config you want to pass
    //     }
    //   }
    // }
  },
})
If you're using the simple setup with environment: 'nuxt' by default, you can opt out of the Nuxt environment per test file as needed.
// @vitest-environment node
import { test } from 'vitest'
test('my test', () => {
  // ... test without Nuxt environment!
})
nuxtApp are not initialized. This can lead to hard-to-debug errors.With the project-based setup, you might organize your tests as follows:
test/
├── e2e/
│   └── ssr.test.ts
├── nuxt/
│   ├── components.test.ts
│   └── composables.test.ts
├── unit/
│   └── utils.test.ts
You can of course opt for any test structure, but keeping the Nuxt runtime environment separated from Nuxt end-to-end tests is important for test stability.
With the project setup, you can run different test suites:
# Run all tests
npx vitest
# Run only unit tests
npx vitest --project unit
# Run only Nuxt tests
npx vitest --project nuxt
# Run tests in watch mode
npx vitest --watch
happy-dom or 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).@nuxt/test-utils provides some built-in mocks for the DOM environment.
intersectionObserverDefault true, creates a dummy class without any functionality for the IntersectionObserver API
indexedDBDefault 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.ts file:
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
  test: {
    environmentOptions: {
      nuxt: {
        mock: {
          intersectionObserver: true,
          indexedDb: true,
        },
      },
    },
  },
})
@nuxt/test-utils provides a number of helpers to make testing Nuxt apps easier.
mountSuspendedmountSuspended allows you to mount any Vue component within the Nuxt environment, allowing async setup and access to injections from your Nuxt plugins.
mountSuspended wraps mount from @vue/test-utils, so you can check out the Vue Test Utils documentation for more on the options you can pass, and how to use this utility.For example:
// tests/components/SomeComponents.nuxt.spec.ts
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'
it('can mount some component', async () => {
  const component = await mountSuspended(SomeComponent)
  expect(component.text()).toMatchInlineSnapshot(
    '"This is an auto-imported component"',
  )
})
// tests/components/SomeComponents.nuxt.spec.ts
import { mountSuspended } from '@nuxt/test-utils/runtime'
import App from '~/app.vue'
// 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>"
    `)
})
renderSuspendedrenderSuspended 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:
// tests/components/SomeComponents.nuxt.spec.ts
import { renderSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'
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'
import App from '~/app.vue'
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>"
  `)
})
mockNuxtImportmockNuxtImport allows you to mock Nuxt's auto import functionality. For example, to mock useStorage, you can do so like this:
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport('useStorage', () => {
  return () => {
    return { value: 'mocked storage' }
  }
})
// your tests here
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 in the Vitest docs.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/test-utils/runtime'
const { useStorageMock } = vi.hoisted(() => {
  return {
    useStorageMock: vi.fn(() => {
      return { value: 'mocked storage' }
    }),
  }
})
mockNuxtImport('useStorage', () => {
  return useStorageMock
})
// Then, inside a test
useStorageMock.mockImplementation(() => {
  return { value: 'something else' }
})
mockComponentmockComponent 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/test-utils/runtime'
mockComponent('MyComponent', {
  props: {
    value: String,
  },
  setup (props) {
    // ...
  },
})
// relative path or alias also works
mockComponent('~/components/my-component.vue', () => {
  // or a factory function
  return defineComponent({
    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.
import { mockComponent } from '@nuxt/test-utils/runtime'
mockComponent('MyComponent', async () => {
  const { ref, h } = await import('vue')
  return defineComponent({
    setup (props) {
      const counter = ref(0)
      return () => h('div', null, counter.value)
    },
  })
})
registerEndpointregisterEndpoint 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/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.
import { registerEndpoint } from '@nuxt/test-utils/runtime'
registerEndpoint('/test/', {
  method: 'POST',
  handler: () => ({ test: 'test-field' }),
})
Note: If your requests in a component go to an external API, you can use
baseURLand then make it empty using Nuxt Environment Override Config ($test) so all your requests will go to Nitro server.
@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
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport('useStorage', () => {
  return () => {
    return { value: 'mocked storage' }
  }
})
app.e2e.spec.ts
import { $fetch, setup } from '@nuxt/test-utils/e2e'
await setup({
  setupTimeout: 10000,
})
// ...
@vue/test-utilsIf you prefer to use @vue/test-utils on its own for unit testing in Nuxt, and you are only testing components which do not rely on Nuxt composables, auto-imports or context, you can follow these steps to set it up.
npm i --save-dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
yarn add --dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
pnpm add -D vitest @vue/test-utils happy-dom @vitejs/plugin-vue
bun add --dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
vitest.config.ts with the following content:import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'happy-dom',
  },
})
package.json"scripts": {
  "build": "nuxt build",
  "dev": "nuxt dev",
  ...
  "test": "vitest"
},
<HelloWorld> component components/HelloWorld.vue with the following content:<template>
  <p>Hello world</p>
</template>
~/components/HelloWorld.spec.tsimport { describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import HelloWorld from './HelloWorld.vue'
describe('HelloWorld', () => {
  it('component renders Hello world properly', () => {
    const wrapper = mount(HelloWorld)
    expect(wrapper.text()).toContain('Hello world')
  })
})
npm run test
yarn test
pnpm run test
bun run test
Congratulations, you're all set to start unit testing with @vue/test-utils in Nuxt! Happy testing!
For end-to-end testing, we support Vitest, Jest, Cucumber and Playwright as test runners.
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.
import { describe, test } from 'vitest'
import { $fetch, setup } from '@nuxt/test-utils/e2e'
describe('My test', async () => {
  await setup({
    // test context options
  })
  test('my test', () => {
    // ...
  })
})
Behind the scenes, setup performs a number of tasks in beforeAll, beforeEach, afterEach and afterAll to set up the Nuxt test environment correctly.
Please use the options below for the setup method.
rootDir: Path to a directory with a Nuxt app to be put under test.
string'.'configFile: Name of the configuration file.
string'nuxt.config'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).number120000 or 240000 on windowsteardownTimeout: The amount of time (in milliseconds) to allow tearing down the test environment, such as closing the browser.number30000build: Whether to run a separate build step.booleantrue (false if browser or server is disabled, or if a host is provided)server: Whether to launch a server to respond to requests in the test suite.booleantrue (false if a host is provided)port: If provided, set the launched test server port to the value.number | undefinedundefinedhost: If provided, a URL to use as the test target instead of building and running a new server. Useful for running "real" end-to-end tests against a deployed version of your application, or against an already running local server (which may provide a significant reduction in test execution timings). See the target host end-to-end example below.stringundefinedbrowser: Under the hood, Nuxt test utils uses playwright to carry out browser testing. If this option is set, a browser will be launched and can be controlled in the subsequent test suite.booleanfalsebrowserOptionsobject with the following properties
type: The type of browser to launch - either chromium, firefox or webkitlaunch: object of options that will be passed to playwright when launching the browser. See full API reference.runner: Specify the runner for the test suite. Currently, Vitest is recommended.'vitest' | 'jest' | 'cucumber''vitest'host end-to-end exampleA common use-case for end-to-end testing is running the tests against a deployed application running in the same environment typically used for Production.
For local development or automated deploy pipelines, testing against a separate local server can be more efficient and is typically faster than allowing the test framework to rebuild between tests.
To utilize a separate target host for end-to-end tests, simply provide the host property of the setup function with the desired URL.
import { createPage, setup } from '@nuxt/test-utils/e2e'
import { describe, expect, it } from 'vitest'
describe('login page', async () => {
  await setup({
    host: 'http://localhost:8787',
  })
  it('displays the email and password fields', async () => {
    const page = await createPage('/login')
    expect(await page.getByTestId('email').isVisible()).toBe(true)
    expect(await page.getByTestId('password').isVisible()).toBe(true)
  })
})
$fetch(url)Get the HTML of a server-rendered page.
import { $fetch } from '@nuxt/test-utils/e2e'
const html = await $fetch('/')
fetch(url)Get the response of a server-rendered page.
import { fetch } from '@nuxt/test-utils/e2e'
const res = await fetch('/')
const { body, headers } = res
url(path)Get the full URL for a given page (including the port the test server is running on.)
import { url } from '@nuxt/test-utils/e2e'
const pageUrl = url('/page')
// 'http://localhost:6840/page'
We provide built-in support using Playwright within @nuxt/test-utils, either programmatically or via the Playwright test runner.
createPage(url)Within vitest, jest or cucumber, you can create a configured Playwright browser instance with createPage, 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.
import { createPage } from '@nuxt/test-utils/e2e'
const page = await createPage('/page')
// you can access all the Playwright APIs from the `page` variable
We also provide first-class support for testing Nuxt within the Playwright test runner.
npm i --save-dev @playwright/test @nuxt/test-utils
yarn add --dev @playwright/test @nuxt/test-utils
pnpm add -D @playwright/test @nuxt/test-utils
bun add --dev @playwright/test @nuxt/test-utils
You can provide global Nuxt configuration, with the same configuration details as the setup() function mentioned earlier in this section.
import { fileURLToPath } from 'node:url'
import { defineConfig, devices } from '@playwright/test'
import type { ConfigOptions } from '@nuxt/test-utils/playwright'
export default defineConfig<ConfigOptions>({
  use: {
    nuxt: {
      rootDir: fileURLToPath(new URL('.', import.meta.url)),
    },
  },
  // ...
})
Your test file should then use expect and test directly from @nuxt/test-utils/playwright:
import { expect, test } from '@nuxt/test-utils/playwright'
test('test', async ({ page, goto }) => {
  await goto('/', { waitUntil: 'hydration' })
  await expect(page.getByRole('heading')).toHaveText('Welcome to Playwright!')
})
You can alternatively configure your Nuxt server directly within your test file:
import { expect, test } from '@nuxt/test-utils/playwright'
test.use({
  nuxt: {
    rootDir: fileURLToPath(new URL('..', import.meta.url)),
  },
})
test('test', async ({ page, goto }) => {
  await goto('/', { waitUntil: 'hydration' })
  await expect(page.getByRole('heading')).toHaveText('Welcome to Playwright!')
})