Testing strategy

Testing strategy

How to talk about testing as a senior — confidence per unit of cost, the testing trophy for a Nuxt app, what belongs at each level, mocking at boundaries, flake policy — and how the pages on Vitest, component tests with @nuxt/test-utils, Playwright, API tests and CI quality gates fit together.

"How do you test a Nuxt application" is a question about judgement: which tests give confidence for their cost, where the seams are, what to mock and what never to mock. The toolchain in 2026 is settled: Vitest 5 (released 3 September 2026) with @nuxt/test-utils 4 for unit and component tests in a Nuxt runtime environment, and Playwright for end-to-end. What separates candidates is the strategy.

Know

  • Confidence per cost. Unit tests are cheap and fast but prove little about integration; end-to-end tests prove the product works but are slow and flaky; component and integration tests in the middle give the best return for UI code. The "testing trophy" (mostly integration, some unit for logic, few end-to-end for critical flows, static analysis underneath) fits a Nuxt app better than the classic pyramid.
  • Test behaviour, not implementation. Component tests interact through the accessible interface (roles, labels, text) and assert on rendered output and emitted events; they do not reach into wrapper.vm or assert on internal state. Refactors should not break tests.
  • Mock at boundaries, not inside. Network through registerEndpoint or MSW, time through fake timers, the Nuxt context through mockNuxtImport; never mock the module you are testing or its siblings. If a unit test needs five mocks, the code needs a seam.
  • Static analysis is a test tier: nuxt typecheck, ESLint with type-aware rules and vue-tsc catch a class of bugs no runtime test finds, for free on every commit.
  • Environments: pure logic runs in Node; components and composables that use auto-imports and Nuxt plugins run in the nuxt Vitest environment with happy-dom or jsdom; visual and browser-API-heavy components can run in Vitest browser mode (Playwright provider); user flows run in Playwright against a built app.
  • Flakiness is a defect. Fix the root cause (a missing await, a race on hydration, a test depending on order), quarantine with an owner and a deadline if you cannot fix it now, and never let retries hide a real race in production code.
  • Nuxt specifics: SSR means a test can render on the server ($fetch('/') in e2e) and in the browser; hydration errors are testable (listen for console warnings); useAsyncData needs the Nuxt environment or registerEndpoint; stores need createTestingPinia; data fetching in setup needs mountSuspended, which handles Suspense.

How the pages fit

PageWhat it covers
Unit tests with VitestVitest 5 essentials, testing utilities, composables and stores, fake timers, MSW, snapshots, coverage
Component testsVue Test Utils versus Testing Library, mountSuspended, mocking Nuxt imports, endpoints and components, Suspense, browser mode
End-to-end with PlaywrightLocators and auto-waiting, @nuxt/test-utils/e2e and the Playwright fixture, auth state, network mocking, hydration checks, visual and accessibility assertions
Server and API testsTesting Nitro routes, databases in tests, contract tests, seeding and isolation
Quality gates and CIPipeline design, sharding, coverage policy, flake management, budgets, preview environments

How it works

The strategy for one feature, "add to cart", across the levels:

Static:      nuxt typecheck + eslint            -> the CartItem type, the emitted payloads, floating promises
Unit:        cart totals, coupon rules           -> pure functions in shared/, dozens of cases in ms
Store:       useCartStore add/remove/checkout    -> createTestingPinia + registerEndpoint for /api/orders
Component:   <CartSummary>, <AddToCartButton>    -> mountSuspended, getByRole, emitted events, a11y roles
API:         POST /api/orders                    -> validation 400s, auth 401, happy path against a test DB
E2E (few):   browse -> add -> checkout           -> Playwright against the built app, one browser, storageState for login
Ops:         Lighthouse budget, axe wcag22aa     -> in CI on the preview deployment
vitest.config.ts
import { defineConfig } from 'vitest/config'
import { defineVitestProject } from '@nuxt/test-utils/config'

export default defineConfig({
  test: {
    projects: [
      { test: { name: 'unit', include: ['test/unit/**/*.spec.ts'], environment: 'node' } },
      await defineVitestProject({
        test: {
          name: 'nuxt',
          include: ['test/nuxt/**/*.spec.ts'],
          environment: 'nuxt',
          environmentOptions: { nuxt: { domEnvironment: 'happy-dom', mock: { intersectionObserver: true } } },
        },
      }),
    ],
    coverage: { provider: 'v8', include: ['app/**', 'shared/**', 'server/**'], thresholds: { statements: 70 } },
  },
})
Gotcha· Coverage as the goal

A 95% coverage target produces tests that execute lines without asserting anything meaningful, and it penalises deleting code. Coverage is a map of what is untested, useful for spotting a critical module with nothing around it; the target is confidence in the flows that matter.

Exercise

Exercise
  • Take one feature in a project you own and write the table above for it: what is tested at which level today, and what is missing.
  • Set up the two-project Vitest configuration in a Nuxt app and run npx vitest --project unit and --project nuxt separately; time both.

Be able to say

Be able to say· How do you approach testing a Nuxt application?

"By confidence per cost. Static analysis first, because nuxt typecheck and type-aware lint catch a whole class of bugs on every commit. Pure logic, pricing rules, formatters, validators, gets fast unit tests in Node. Most of the budget goes to component and integration tests in the Nuxt Vitest environment with mountSuspended: they interact through roles and labels, assert on rendered output and emitted events, and mock only at boundaries, the network through registerEndpoint, stores through createTestingPinia. A few Playwright end-to-end tests cover the critical flows against the built app, with login state reused and hydration warnings failing the test. Accessibility and performance budgets run against the preview deployment. And flaky tests are treated as bugs with owners, not as noise to retry."

Pages in this section

  1. 01Unit tests with VitestVitest 5 essentials — projects, matchers, spies and mocks, fake timers, snapshots, type tests, coverage — and how to unit-test the parts of a Nuxt app that need no DOM — utilities, composables, Pinia stores, Leitner scheduling — with MSW for network boundaries.
  2. 02Component testsVue Test Utils versus Testing Library, the Nuxt Vitest environment with mountSuspended and renderSuspended, mocking auto-imports, components and endpoints, testing async setup and Suspense, emitted events and slots, stores with createTestingPinia, and when Vitest browser mode is worth it.
  3. 03End-to-end with PlaywrightPlaywright's model — locators, auto-waiting, web-first assertions, fixtures, tracing — applied to Nuxt through @nuxt/test-utils/e2e and the Playwright fixture, with reusable auth state, network mocking, hydration checks, accessibility and visual assertions, sharding and a flake policy.
  4. 04Server and API testsTesting Nitro routes at three levels — handler logic in isolation, routes through @nuxt/test-utils/e2e, and contracts against an OpenAPI document — with databases in tests (SQLite in memory, Testcontainers, transaction rollback), seeding and isolation, auth in tests, and validation and error-shape assertions.
  5. 05Quality gates and CIDesigning the pipeline — stage order, caching, sharding and parallelism, what blocks a merge — plus coverage policy, flake management, accessibility and performance budgets, visual regression, type and mutation tests, preview deployments, and the test-data and environment story.

Your notes

Open in Notes

Nothing yet. Notes filed here show up on every page of this section.