Nuxt test-utils: setup, examples, and testing patterns

You can ship a Nuxt app fast, then lose days chasing regressions because one prop changed or a route moved. Good tests pay you back on every deploy. nuxt test-utils gives you a Nuxt-aware test environment so you can validate components, composables, server routes, and pages with real confidence.
My rule for SaaS and AI tools: test what earns or protects revenue first. That means signup, login, checkout, webhooks, locale routing, and admin permission checks. Then cover the glue code you are afraid to touch. If you are weighing a Nuxt starter kit or a Nuxt SaaS boilerplate to ship fast, put testing in the plan from day one. That is true whether you are building a small dashboard, a subscription app, or asking yourself, how do I build and sell an AI tool online?
What nuxt test-utils actually provides
nuxt test-utils boots a real Nuxt context inside Vitest. Your auto-imports, runtime config, plugins, middleware, and the Nitro server behave like they do in dev. Concretely, you can:
- Mount Vue components with Nuxt plugins and auto-imports available using
mountSuspended. Async setup and Suspense are resolved before assertions. - Call server routes with
$fetchagainst a live Nitro instance that starts once for your suite. - Open a browser page for lightweight end-to-end checks with
createPage(Playwright under the hood).
It complements @vue/test-utils for deep component interactions and Playwright for full-browser automation. You get fast unit and integration feedback without wiring a custom server by hand.
Set up nuxt test-utils with Vitest (step by step)
- Install dev dependencies.
npm i -D vitest nuxt-vitest @nuxt/test-utils @vue/test-utils - Enable the Vitest module. In
nuxt.config.tsaddmodules: ['nuxt-vitest']. If you need test-only config, settestkeys insideruntimeConfig. - Create a Vitest config. In
vitest.config.ts:export default defineConfig({ test: { environment: 'nuxt', globals: true, setupFiles: ['./tests/setup.ts'] } }). The setup file is optional but handy formockNuxtImportand test-wide hooks. - Add a first test. Create
tests/components/Counter.spec.ts. Example:const wrapper = await mountSuspended(Counter); await wrapper.get('button').trigger('click'); expect(wrapper.text()).toContain('1'). - Run tests.
npx vitestfor watch mode, ornpx vitest --runin CI. If you will usecreatePage, runnpx playwright installonce.
That is enough to mount components with a live Nuxt context and to hit server routes in tests.
Write tests that cover real Nuxt behavior
1) Components: mount with Nuxt context
Use mountSuspended from @nuxt/test-utils/runtime so async setup and Suspense complete before assertions. This avoids race conditions that appear with a plain Vue mount.
- Mount with props.
const wrapper = await mountSuspended(MyButton, { props: { label: 'Pay now' } }). - Assert DOM and events.
await wrapper.get('button').trigger('click')thenexpect(wrapper.emitted('click')).toBeTruthy(). - Provide plugins or mocks. Pass
global.plugins(e.g., your i18n instance) orglobal.mocksfor injected keys. - Router-aware UI. If the component reads route params, mock them:
mockNuxtImport('useRoute', () => () => ({ params: { id: '42' }, query: {} })).
Tip: If your component reads useRuntimeConfig(), do not hand-roll a stub. Prefer setting runtimeConfig for the test environment in nuxt.config.ts, or mock the import with mockNuxtImport('useRuntimeConfig', ...) for a single spec.
2) Composables: isolate logic, mock Nuxt imports
Composables often call useFetch, useState, or useRuntimeConfig. Mock those auto-imports so unit tests never hit the network.
- Mock a runtime value.
mockNuxtImport('useRuntimeConfig', () => () => ({ public: { apiBase: '/api' } })). - Mock
useFetchwith reactive results.mockNuxtImport('useFetch', () => async () => ({ data: { value: { ok: true } }, pending: { value: false }, error: { value: null } })). Return refs fordata,pending, anderrorto match real behavior. - Exercise branches. Write one test for success, one for
error, one forpending. Add a case for 401 to confirm your refresh or logout path.
Run the composable inside a tiny component with mountSuspended, or export pure helpers for direct testing.
3) Server routes: call Nitro with $fetch
Start Nitro once at the suite level, then hit endpoints with $fetch. You get real middleware, auth checks, and runtime config.
- Boot the test context.
import { setup, $fetch } from '@nuxt/test-utils'andawait setup({})intests/setup.ts. - Call endpoints.
const res = await $fetch('/api/health'); assert status and JSON shape. - Seed data. Prepare rows in a test database in
beforeEach, clean up inafterEach. Use an isolated schema or an in-memory DB to avoid cross-test bleed. - Auth headers. For cookie auth, pass
headers: { cookie: 'session=...' }. For token auth, setauthorization: 'Bearer <token>'and assert 401/403 on bad tokens.
For apps with webhooks or subscription billing, route tests catch mistakes that unit tests miss, like missing auth headers or wrong JSON. If you run a tool like MeliBoost, you know how many tiny flows can break if you do not guard them.
4) Pages and middleware: browser checks with createPage
For a quick smoke test, open a page in a real browser context.
- Enable the browser.
await setup({ browser: true }). - Open a page.
const page = await createPage('/'), thenawait page.locator('h1').waitFor(). - Assert UI. Use stable selectors:
await expect(await page.textContent('[data-testid="title"]')).toContain('Dashboard').
Keep these short. Use them to catch routing, middleware, or rendering errors, not to click through your whole app. Put full flows in a separate Playwright suite if you need them.
Fit testing into a Nuxt SaaS workflow
If you start from a Nuxt SaaS starter kit or a Vue/Nuxt starter template, you already have authentication, protected pages, checkout, i18n, and a landing page. Test those building blocks first because they move most during early changes.
- Authentication and protected routes. Unit: stub the session or token and verify guards render the right state. Server: assert 401 for unauthenticated requests and 200 for valid cookies on a sample protected route.
- Payments and checkout. Use provider test mode or your mock server. Assert idempotency on retries, subscription status transitions, and error branches (insufficient funds, canceled checkout, expired webhook signature).
- i18n. Mount with at least two locales. Assert a few high-value keys render and that the locale switcher updates route and head tags.
- SEO and
useSeoMeta. For critical pages, assert a non-empty<title>, canonical URL where applicable, and language meta when locale changes. - Admin roles. Verify role-based middleware blocks non-admins and hides admin-only UI controls.
Practical structure that scales:
tests/componentsfor isolated UItests/composablesfor logictests/serverfor Nitro routes and webhookstests/browserfor a handful of smoke tests
Add tiny helpers that pay off quickly: loginAs(user) to set a cookie in tests, factory({ table: 'users' }) to create rows, and withLocale(locale, fn) to swap language during a spec.
Pitfalls and CI without flakes
- Tests hang on Suspense. Use
mountSuspended, await the mount, and avoid triggering async work after assertions. - Auto-imports not found. Ensure
environment: 'nuxt'invitest.config.tsandmodules: ['nuxt-vitest']innuxt.config.ts. - Network calls in unit tests. Mock
useFetchor your HTTP client. Keep real calls in server route tests with$fetch. - Stale Nitro state. If a test needs a fresh server, isolate it in its own file or reset state in
afterEach. Prefer stateless handlers. - Playwright missing. If
createPagefails, runnpx playwright installand rerun the suite. - ESM/TypeScript hiccups. Align
tsconfig.jsonwith Nuxt defaults. Avoid CJS-only libraries in ESM tests. - Pin execution in CI. Use the same Node version locally and in CI. Cache Playwright browsers to speed up runs.
- One command. Add
"test": "vitest --run"topackage.json. Add--reporter=junitif CI expects JUnit XML. - Seed and isolate data. Run migrations before the suite, use a separate test database, and clean up per test. Parallel runs should not share state.
- Coverage that matters. Enable
coverageinvitest.config.ts. Focus on paths that guard money, like signup and checkout, not every branch of a spinner component.
Key takeaways
- nuxt test-utils runs a real Nuxt context inside Vitest so your tests mirror production behavior.
- Use
mountSuspendedfor components,$fetchfor server routes, andcreatePagefor quick browser checks. - Mock Nuxt auto-imports in composable tests to avoid network calls and flakes.
- Prioritize auth, protected pages, payments, and locale routing in a Nuxt SaaS template.
- Keep browser tests short; let unit and integration tests carry most coverage.
If you already work from a Nuxt SaaS template or a Nuxt SaaS starter kit, plug these patterns in now. They will keep you shipping without fear when features like authentication, payments, multi-language support, admin tools, transactional emails, analytics, and SEO settings start to change.
FAQ
What is the difference between nuxt test-utils and @vue/test-utils?
nuxt test-utils boots a Nuxt-aware test environment and Nitro server. @vue/test-utils mounts Vue components. Use them together to test Nuxt components with real context.
How do I test a Nuxt server route with Vitest?
Call setup from @nuxt/test-utils, then use $fetch to hit your endpoint. Seed any required data before the request and assert the response shape.
Do I need Playwright to use createPage?
Yes. createPage relies on Playwright. Install it once with npx playwright install, then you can open real pages in tests.
How can I mock useFetch in a composable test?
Use mockNuxtImport from nuxt-vitest/utils to stub useFetch and return a predictable data value, pending state, and error.
Should I still write tests if I use a Nuxt SaaS starter kit?
Yes. A starter kit helps you ship fast, but tests protect key flows like auth, checkout, and i18n when you start changing code.
Ready to ship your SaaS?
Nuxt Stripe payments for one-time and subscription billing
Set up Stripe payments in Nuxt: one-time checkout, subscriptions, trials, webhooks, testing, and launch steps with concrete examples you can ship today.
nuxt/test-utils: Practical testing for Nuxt 3 SaaS apps
Set up nuxt/test-utils with Vitest to test pages, APIs, auth, i18n, and payments in Nuxt 3. Real examples, CI tips, and pitfalls to ship with confidence.