Nuxt Testing — Unit Tests, Component Tests, and E2E Tests with Vitest
In this tutorial, you will learn about Nuxt Testing. We cover key concepts, practical examples, and best practices to help you master this topic.
Learn Nuxt testing — write unit tests for composables and utilities, component tests with Vue Test Utils, and end-to-end tests with Playwright using Vitest.
In this lesson, you'll understand how to set up a testing environment for Nuxt and write tests at every level of your application.
What You'll Learn
How to install and configure Vitest with @nuxt/test-utils, write unit tests for composables and utility functions, test Vue components with mounting and assertions, write end-to-end tests with Playwright, and run tests in CI pipelines.
Why It Matters
Automated tests catch regressions before they reach production, reduce manual QA time, and give you confidence to refactor code. A Nuxt app without tests becomes harder to maintain as it grows — every change risks breaking existing functionality.
Real-World Use
A Nuxt-based checkout system at an e-commerce company uses 300+ unit tests and 50+ E2E tests that run on every pull request, catching 95% of regressions before deployment and reducing production incidents by 80%.
flowchart LR
A[Test Pyramid] --> B[Unit Tests]
A --> C[Component Tests]
A --> D[E2E Tests]
B --> E[Composables]
B --> F[Utilities]
C --> G[Vue Components]
C --> H[Composables in Context]
D --> I[User Flows]
D --> J[Full Page Rendering]
style A fill:#00dc82,color:#fff
Setup
Install testing dependencies:
npm install --save-dev vitest @vue/test-utils @nuxt/test-utils jsdom
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./tests/setup.ts'],
include: ['**/*.test.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'json'],
include: ['composables/**', 'utils/**', 'components/**']
}
},
resolve: {
alias: {
'~': '/',
'@': '/'
}
}
});
// tests/setup.ts
import { vi } from 'vitest';
// Mock Nuxt composables
vi.mock('nuxt/app', () => ({
useRuntimeConfig: () => ({
public: {
apiBase: 'https://api.example.com'
}
}),
useState: vi.fn((key, init) => {
const state = ref(init?.());
return state;
})
}));
Testing Composables
Tests for a composable that formats currency:
// composables/useCurrency.ts
export function useCurrency() {
function format(amount: number, currency: string = 'USD'): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency
}).format(amount);
}
function parse(formatted: string): number {
const cleaned = formatted.replace(/[$,€£¥]/g, '');
return parseFloat(cleaned);
}
return { format, parse };
}
// tests/composables/useCurrency.test.ts
import { describe, it, expect } from 'vitest';
import { useCurrency } from '~/composables/useCurrency';
describe('useCurrency', () => {
const { format, parse } = useCurrency();
it('formats USD correctly', () => {
expect(format(19.99)).toBe('$19.99');
});
it('formats EUR correctly', () => {
expect(format(42.50, 'EUR')).toBe('€42.50');
});
it('parses formatted string back to number', () => {
expect(parse('$1,234.56')).toBe(1234.56);
});
it('handles zero', () => {
expect(format(0)).toBe('$0.00');
});
it('handles large numbers', () => {
expect(format(1000000)).toBe('$1,000,000.00');
});
});
Expected output: All five tests pass, confirming the currency composable handles edge cases correctly.
Testing Components
Test a button component:
<!-- components/PrimaryButton.vue -->
<template>
<button
:class="['primary-btn', { 'primary-btn--loading': loading }]"
:disabled="loading || disabled"
@click="$emit('click')"
>
<span v-if="loading" class="spinner" />
<slot />
</button>
</template>
<script setup>
defineProps({
loading: { type: Boolean, default: false },
disabled: { type: Boolean, default: false }
});
defineEmits(['click']);
</script>
// tests/components/PrimaryButton.test.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import PrimaryButton from '~/components/PrimaryButton.vue';
describe('PrimaryButton', () => {
it('renders slot content', () => {
const wrapper = mount(PrimaryButton, {
slots: { default: 'Submit' }
});
expect(wrapper.text()).toContain('Submit');
});
it('emits click event when clicked', async () => {
const wrapper = mount(PrimaryButton, {
slots: { default: 'Click' }
});
await wrapper.trigger('click');
expect(wrapper.emitted('click')).toBeTruthy();
expect(wrapper.emitted('click').length).toBe(1);
});
it('does not emit click when disabled', async () => {
const wrapper = mount(PrimaryButton, {
props: { disabled: true },
slots: { default: 'Disabled' }
});
await wrapper.trigger('click');
expect(wrapper.emitted('click')).toBeFalsy();
});
it('shows spinner when loading', () => {
const wrapper = mount(PrimaryButton, {
props: { loading: true },
slots: { default: 'Loading' }
});
expect(wrapper.find('.spinner').exists()).toBe(true);
expect(wrapper.find('.primary-btn--loading').exists()).toBe(true);
});
});
Expected output: All component tests pass, verifying rendering, event emission, and prop-driven states.
Testing Pages with @nuxt/test-utils
Use @nuxt/test-utils for full-page integration tests:
// tests/pages/index.test.ts
import { describe, it, expect } from 'vitest';
import { setup, $fetch } from '@nuxt/test-utils/e2e';
describe('Homepage', async () => {
await setup({
rootDir: './',
server: true,
browser: false
});
it('renders the homepage', async () => {
const html = await $fetch('/');
expect(html).toContain('Welcome');
expect(html).toContain('Get Started');
});
it('includes SEO meta tags', async () => {
const html = await $fetch('/');
expect(html).toContain('<title>');
expect(html).toContain('meta name="description"');
});
it('loads without server errors', async () => {
const response = await $fetch('/');
expect(response).not.toContain('Internal Server Error');
});
});
Expected output: Integration tests that render the actual Nuxt app and verify HTML output and SEO tags.
End-to-End Tests with Playwright
Set up Playwright for browser-based testing:
// tests/e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
test('user can log in', async ({ page }) => {
// Navigate to login page
await page.goto('/login');
// Fill credentials
await page.fill('[name="email"]', 'user@example.com');
await page.fill('[name="password"]', 'password123');
// Submit form
await page.click('button[type="submit"]');
// Verify redirect to dashboard
await expect(page).toHaveURL(/\/dashboard/);
await expect(page.locator('h1')).toContainText('Welcome');
});
test('invalid credentials show error', async ({ page }) => {
await page.goto('/login');
await page.fill('[name="email"]', 'wrong@email.com');
await page.fill('[name="password"]', 'wrongpassword');
await page.click('button[type="submit"]');
// Verify error message
await expect(page.locator('.error-message')).toBeVisible();
await expect(page.locator('.error-message')).toContainText('Invalid credentials');
});
test('unauthenticated user is redirected', async ({ page }) => {
await page.goto('/dashboard');
await expect(page).toHaveURL(/\/login/);
});
Expected output: Playwright tests that simulate real user interactions in a browser, verifying authentication flows end-to-end.
Running Tests
# Run unit and component tests
npx vitest
# Run with coverage
npx vitest --coverage
# Run E2E tests
npx playwright test
# Run all tests
npx vitest && npx playwright test
Expected output: All tests pass with a summary showing passed/failed counts and coverage percentages.
Common Mistakes
Not mocking Nuxt composables: Composables like
useFetch,useState, anduseRouterequire mocking in unit tests. Without mocks, tests fail because the Nuxt runtime is not available.Testing implementation details instead of behavior: Test what the component does (renders content, emits events), not how it does it (internal state variables, method names). Behavioral tests survive Refactoring.
Forgetting to clean up test environment: Test files that modify global state or mocks can affect other tests. Use
afterEachorafterAllhooks to reset mocks and clean up mounted components.Not testing error states: Tests often cover the "happy path" but skip error and loading states. Test what happens when data fetching fails, when props are invalid, or when network is unavailable.
Running slow tests without grouping: E2E tests take longer than unit tests. Group them separately and run unit tests on every commit, E2E tests only before deployment or on PR merges.
Practice Questions
What is the difference between unit tests and component tests? Answer: Unit tests test individual functions or composables in isolation. Component tests mount Vue components and test rendering, events, and props in a simulated DOM environment.
Why must you mock Nuxt composables in unit tests? Answer: Nuxt composables depend on the Nuxt runtime, which is not available in a standard Vitest environment. Mocks provide the expected return values without running Nuxt itself.
What does @nuxt/test-utils provide for Integration Testing? Answer: It starts a Nuxt server in the test environment, allowing tests to make HTTP requests to pages and API routes, verifying actual HTML output and server responses.
When should you use E2E tests instead of unit tests? Answer: Use E2E tests for critical user flows (login, checkout, registration) that involve multiple pages and interactions. Use unit tests for isolated logic that can be verified independently.
Challenge
Set up a complete testing infrastructure with: unit tests for three composables covering edge cases, component tests for three core components (button, form input, modal) including error states, an E2E test that completes a full user flow (register, login, create content, log out), CI configuration that runs unit tests on every push and E2E on PRs, and a coverage threshold of 80%.
Mini Project
Add a test suite to an existing Nuxt project with: unit tests for all utility functions and composables (minimum 90% coverage), component tests for shared UI components (button, card, modal, form input), page tests for the homepage and one detail page using @nuxt/test-utils, E2E tests for the primary user flow (registration to content creation), and a test:ci npm script that runs all tests and generates a coverage report.
FAQ
What's Next
Learn about Nuxt Environment Variables and Configuration to manage runtime configuration, environment variables, and per-environment settings in your Nuxt application.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro