Harness-engineering nuxt-testing-patterns

Nuxt Testing Patterns

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/nuxt-testing-patterns" ~/.claude/skills/intense-visions-harness-engineering-nuxt-testing-patterns && rm -rf "$T"
manifest: agents/skills/claude-code/nuxt-testing-patterns/SKILL.md
source content

Nuxt Testing Patterns

Test Nuxt components, composables, and pages using @nuxt/test-utils with full Nuxt context including auto-imports

When to Use

  • You are writing unit or integration tests for Nuxt components or pages that use Nuxt composables
  • You need to mock
    useFetch
    ,
    useRoute
    , or other Nuxt auto-imports in tests
  • You are testing async components that use
    <Suspense>
    or
    useAsyncData
  • You need end-to-end tests against a running Nuxt server using Playwright

Instructions

Setup:

  1. Install
    @nuxt/test-utils
    and configure Vitest with the Nuxt environment:
// vitest.config.ts
import { defineVitestConfig } from '@nuxt/test-utils/config';

export default defineVitestConfig({
  test: {
    environment: 'nuxt',
    environmentOptions: {
      nuxt: {
        rootDir: '.',
      },
    },
  },
});

Mounting components:

  1. Use
    mountSuspended
    instead of
    mount
    for components that use
    useAsyncData
    ,
    useFetch
    , or other async composables. It wraps the component in
    <Suspense>
    and waits for resolution:
import { mountSuspended } from '@nuxt/test-utils/runtime';
import MyComponent from '~/components/MyComponent.vue';

it('renders user data', async () => {
  const wrapper = await mountSuspended(MyComponent, {
    props: { userId: '123' },
  });
  expect(wrapper.text()).toContain('John Doe');
});
  1. Use
    renderSuspended
    for testing with
    @testing-library/vue
    patterns:
import { renderSuspended } from '@nuxt/test-utils/runtime';
import { screen } from '@testing-library/vue';

it('shows the title', async () => {
  await renderSuspended(MyPage);
  expect(screen.getByRole('heading')).toHaveTextContent('Dashboard');
});

Mocking Nuxt composables:

  1. Use
    mockNuxtImport
    to mock any auto-imported composable. Call it once at the top of the test file — it persists across the file:
import { mockNuxtImport } from '@nuxt/test-utils/runtime';

mockNuxtImport('useFetch', () => {
  return () => ({
    data: ref({ users: [{ id: 1, name: 'Alice' }] }),
    error: ref(null),
    pending: ref(false),
  });
});
  1. Mock
    useRoute
    to simulate navigation params:
mockNuxtImport('useRoute', () => {
  return () => ({
    params: { id: '42' },
    query: { tab: 'settings' },
    path: '/users/42',
  });
});
  1. Use
    vi.mocked
    with
    mockNuxtImport
    for per-test overrides:
const { useFetch } = vi.hoisted(() => ({ useFetch: vi.fn() }));

mockNuxtImport('useFetch', () => useFetch);

beforeEach(() => {
  useFetch.mockReturnValue({
    data: ref(null),
    error: ref('Network error'),
    pending: ref(false),
  });
});

Testing server routes:

  1. Use
    $fetch
    from
    @nuxt/test-utils
    to make real HTTP requests against a test Nuxt server:
import { setup, $fetch } from '@nuxt/test-utils/e2e';

describe('Server routes', async () => {
  await setup({ rootDir: '.' });

  it('returns users', async () => {
    const users = await $fetch('/api/users');
    expect(users).toHaveLength(3);
  });
});

End-to-end testing with Playwright:

  1. Configure Playwright E2E tests that boot a real Nuxt dev server:
// tests/e2e/home.spec.ts
import { setup, createPage, url } from '@nuxt/test-utils/e2e';
import { test, expect } from '@playwright/test';

await setup({ rootDir: '.', browser: true });

test('home page loads', async () => {
  const page = await createPage('/');
  await expect(page.locator('h1')).toHaveText('Welcome');
});

Details

Why not use plain Vitest + @vue/test-utils?

Standard Vue Test Utils doesn't know about Nuxt's auto-import layer. Composables like

useNuxtApp
,
useFetch
,
useRoute
will throw "not in Nuxt context" errors.
@nuxt/test-utils
sets up a minimal Nuxt environment that makes auto-imports available in tests.

mountSuspended
vs.
mount
:

  • mount
    — synchronous, works for components with no async composables
  • mountSuspended
    — wraps in
    <Suspense>
    , awaits all
    useAsyncData
    /
    useFetch
    calls, returns after data is resolved

Always prefer

mountSuspended
for page components or any component using
useAsyncData
.

Mocking modules vs. mocking composables:

mockNuxtImport
mocks at the auto-import resolution layer. It's different from
vi.mock('~/composables/useMyThing')
— the latter only works for explicit imports. Use
mockNuxtImport
for anything that's auto-imported.

Test file organization:

tests/
  unit/
    components/    ← mountSuspended tests
    composables/   ← plain Vitest + vue's renderHook
  integration/
    server/        ← $fetch API tests
  e2e/             ← Playwright tests

Nuxt test environment options:

// vitest.config.ts
environment: 'nuxt',
environmentOptions: {
  nuxt: {
    rootDir: '.',
    overrides: {
      // Override nuxt.config.ts for tests
      runtimeConfig: { public: { apiBase: 'http://localhost:3000' } }
    }
  }
}

Source

https://nuxt.com/docs/getting-started/testing

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. Verify your implementation against the details and edge cases listed above.

Harness Integration

  • Type: knowledge — this skill is a reference document, not a procedural workflow.
  • No tools or state — consumed as context by other skills and agents.

Success Criteria

  • The patterns described in this document are applied correctly in the implementation.
  • Edge cases and anti-patterns listed in this document are avoided.