{"id":213919,"date":"2026-06-05T09:00:21","date_gmt":"2026-06-05T13:00:21","guid":{"rendered":"https:\/\/blog.logrocket.com\/?p=213919"},"modified":"2026-06-05T19:48:04","modified_gmt":"2026-06-05T23:48:04","slug":"advanced-guide-nuxt-testing-mocking","status":"publish","type":"post","link":"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/","title":{"rendered":"An advanced guide to Nuxt testing and mocking"},"content":{"rendered":"<!DOCTYPE html>\n<html><p>Testing a Nuxt application is not the same as testing plain Vue components. Vue is a frontend framework for building reactive UI components; Nuxt is a full-stack framework built on top of Vue that adds server-side rendering, file-based routing, auto-imports, layouts, middleware, Nitro server routes, and deployment conventions.<\/p><img loading=\"lazy\" decoding=\"async\" width=\"895\" height=\"597\" src=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2026\/06\/Build-a-headless-table-engine-in-Vue-3-1.png\" class=\"attachment-full size-full wp-post-image\" alt=\"An advanced guide to Nuxt testing and mocking\" srcset=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2026\/06\/Build-a-headless-table-engine-in-Vue-3-1.png 895w, https:\/\/blog.logrocket.com\/wp-content\/uploads\/2026\/06\/Build-a-headless-table-engine-in-Vue-3-1-300x200.png 300w, https:\/\/blog.logrocket.com\/wp-content\/uploads\/2026\/06\/Build-a-headless-table-engine-in-Vue-3-1-768x512.png 768w\" sizes=\"auto, (max-width: 895px) 100vw, 895px\">\n<p>That extra framework layer is exactly why Nuxt testing needs its own strategy. A standard Vue unit test can verify a component\u2019s props and rendered output, but it will not automatically reproduce Nuxt behavior like <code>useFetch<\/code>, <code>NuxtLink<\/code>, route middleware, or auto-imported composables. For that, you need the right combination of Vitest environments and Nuxt-specific testing utilities.<\/p>\n<p>In this guide, we\u2019ll test a Nuxt application across three tiers:<\/p>\n<ul>\n<li><strong>Unit tests<\/strong> for isolated Vue components, composables, and pure logic<\/li>\n<li><strong>Nuxt runtime tests<\/strong> for components and pages that depend on Nuxt context<\/li>\n<li><strong>End-to-end tests<\/strong> for full app behavior in a real server or browser flow<\/li>\n<\/ul>\n<p>We\u2019ll use <a href=\"https:\/\/nuxt.com\/docs\/4.x\/getting-started\/testing\"><code>@nuxt\/test-utils<\/code><\/a>, <a href=\"https:\/\/github.com\/vuejs\/test-utils\/\"><code>@vue\/test-utils<\/code><\/a>, <a href=\"https:\/\/testing-library.com\/docs\/vue-testing-library\/intro\/\"><code>@testing-library\/vue<\/code><\/a>, <a href=\"https:\/\/vitest.dev\/\">Vitest<\/a>, and Playwright-powered browser utilities. If you are new to mocks, spies, and Vitest\u2019s core APIs, my previous LogRocket guide on <a href=\"https:\/\/blog.logrocket.com\/advanced-guide-vitest-testing-mocking\/\">advanced Vitest testing and mocking<\/a> covers those fundamentals in more depth.<\/p>\n<h2 id=\"why-nuxt-testing-needs-own-guide\">Why Nuxt testing needs its own guide<\/h2>\n<p>Plain Vue testing libraries like <code>@vue\/test-utils<\/code> and <code>@testing-library\/vue<\/code> work well when you are testing ordinary Vue components. They fall short when the component depends on Nuxt-specific behavior.<\/p>\n<p>Here are a few common examples:<\/p>\n<ul>\n<li style=\"padding-bottom: 10px;\"><strong>Auto-imports<\/strong>: Nuxt makes components and composables globally available, but a standard Vitest setup will not resolve them without Nuxt context<\/li>\n<li style=\"padding-bottom: 10px;\"><strong><code>useFetch<\/code> and <code>useAsyncData<\/code><\/strong>: These composables are tied to Nuxt\u2019s data-fetching lifecycle and SSR context<\/li>\n<li style=\"padding-bottom: 10px;\"><strong><code>useRoute<\/code>, <code>navigateTo<\/code>, and route middleware<\/strong>: Routing behavior is difficult to reproduce accurately outside a Nuxt runtime<\/li>\n<li style=\"padding-bottom: 10px;\"><strong>Layouts<\/strong>: Testing a page component in isolation skips the layout wrapper and can miss integration bugs<\/li>\n<li style=\"padding-bottom: 10px;\"><strong>Nitro API routes<\/strong>: Server endpoints need a different testing strategy than client-side Vue components<\/li>\n<\/ul>\n<p>The following diagram shows how Vitest concepts, Vue testing utilities, and Nuxt-specific testing APIs fit together:<\/p>\n<figure style=\"width: 2781px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/paper-attachments.dropboxusercontent.com\/s_5103AA66F1427B1EB1DAA68D3B237BDD69BE2190D7BE6F6D82206DCEFF781726_1777834274963_difference-apis.excalidraw.svg\" alt=\"Multiple Test APIs Are Required to Ensure Comprehensive Test Coverage\" width=\"2781\" height=\"1263\"><figcaption class=\"wp-caption-text\">Multiple test APIs are required to ensure comprehensive test coverage<\/figcaption><\/figure>\n<p>The practical takeaway is simple: use the lightest test environment that still reproduces the behavior you need. Pure Vue behavior can stay in a fast unit test. Nuxt behavior usually needs the <code>nuxt<\/code> Vitest environment. Full user journeys need e2e coverage.<\/p>\n<h2 id=\"companion-project-testing-environments\">Companion project and testing environments<\/h2>\n<p>This article comes with a <a href=\"https:\/\/github.com\/doppelmutzi\/companion-project-nuxt-testing\">companion GitHub project<\/a> that you can follow along with. It is a Nuxt 4 implementation of the popular <a href=\"https:\/\/todomvc.com\/\">TodoMVC<\/a> app, and its tests live in the <code>test<\/code> folder.<\/p>\n<p>Although the examples use Nuxt 4, most of the testing ideas also apply to current Nuxt 3 projects that use <code>@nuxt\/test-utils<\/code>. Before copying code into an existing app, check your installed Nuxt and <code>@nuxt\/test-utils<\/code> versions against the official docs because imports and setup details can change between major versions.<\/p>\n<p style=\"margin-bottom: 15px;\">The companion project uses three testing tiers. Each tier has a different balance of speed, setup complexity, and confidence:<\/p>\n<table>\n<thead>\n<tr>\n<th>Test tier<\/th>\n<th>Vitest environment<\/th>\n<th>Main tools<\/th>\n<th>Best for<\/th>\n<th>Tradeoff<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Unit<\/td>\n<td><code>happy-dom<\/code>, <code>jsdom<\/code>, or <code>node<\/code><\/td>\n<td>Vitest, <code>@testing-library\/vue<\/code>, <code>@vue\/test-utils<\/code><\/td>\n<td>Pure functions, simple components, isolated composables, isolated store logic<\/td>\n<td>Fastest, but may miss Nuxt integration bugs<\/td>\n<\/tr>\n<tr>\n<td>Nuxt runtime \/ integration<\/td>\n<td><code>nuxt<\/code><\/td>\n<td><code>@nuxt\/test-utils\/runtime<\/code>, <code>mountSuspended<\/code>, <code>renderSuspended<\/code>, <code>registerEndpoint<\/code>, <code>mockNuxtImport<\/code><\/td>\n<td>Components or pages that depend on Nuxt routing, auto-imports, modules, <code>useFetch<\/code>, or middleware<\/td>\n<td>More realistic, but more setup<\/td>\n<\/tr>\n<tr>\n<td>E2E<\/td>\n<td><code>node<\/code> with <code>@nuxt\/test-utils\/e2e<\/code><\/td>\n<td><code>setup<\/code>, <code>$fetch<\/code>, <code>fetch<\/code>, <code>createPage<\/code>, Playwright<\/td>\n<td>Full flows involving SSR, routing, API handlers, hydration, and browser interaction<\/td>\n<td>Highest confidence, but slowest and most brittle<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p style=\"margin-top: 15px;\">You usually need all three kinds of tests. The goal is not to optimize for one tier, but to choose the lowest tier that gives you enough confidence.<\/p>\n<h2 id=\"vitest-environments-nuxt-testing\">Vitest environments for Nuxt testing<\/h2>\n<p>Every test runs in a specific <a href=\"https:\/\/vitest.dev\/guide\/environment\">Vitest environment<\/a>. In a Nuxt app, the environment determines how much of the Nuxt runtime is available:<\/p>\n<ul>\n<li style=\"padding-bottom: 10px;\"><strong><code>happy-dom<\/code> or <code>jsdom<\/code><\/strong>: Lightweight browser-like environments for unit tests. There is no Nuxt runtime, so you must stub Nuxt-specific APIs like <code>NuxtLink<\/code>, <code>useRouter<\/code>, or auto-imported composables yourself.<\/li>\n<li style=\"padding-bottom: 10px;\"><strong><code>nuxt<\/code><\/strong>: Boots a Nuxt runtime inside Vitest. Auto-imports work, Nuxt modules initialize, and <code>@nuxt\/test-utils\/runtime<\/code> APIs like <code>registerEndpoint<\/code> become available. This is useful for integration-style tests that need Nuxt context but not a real browser.<\/li>\n<li style=\"padding-bottom: 10px;\"><strong><code>node<\/code> with <code>@nuxt\/test-utils\/e2e<\/code><\/strong>: Starts a real Nuxt server for e2e tests. You can call real server API routes, fetch SSR-rendered HTML, or create a Playwright page for browser interaction.<\/li>\n<\/ul>\n<p>One important constraint: <code>@nuxt\/test-utils\/runtime<\/code> and <code>@nuxt\/test-utils\/e2e<\/code> run in different environments, so keep runtime tests and e2e tests in separate files.<\/p>\n<h2 id=\"setting-up-vitest-nuxt-project\">Setting up Vitest in a Nuxt project<\/h2>\n<p>According to Nuxt\u2019s testing documentation, <code>@nuxt\/test-utils<\/code> ships with optional peer dependencies so you can choose the DOM environment and e2e runner that fit your project. For the examples in this article, install the following packages:<\/p>\n<pre class=\"language-bash hljs\">npm i --save-dev @nuxt\/test-utils vitest @vue\/test-utils @testing-library\/vue happy-dom playwright-core<\/pre>\n<p>Nuxt\u2019s official setup command includes <code>@nuxt\/test-utils<\/code>, <code>vitest<\/code>, <code>@vue\/test-utils<\/code>, <code>happy-dom<\/code>, and <code>playwright-core<\/code>. We also install <code>@testing-library\/vue<\/code> because several examples below use Testing Library\u2019s <code>render<\/code> and <code>screen<\/code> APIs.<\/p>\n<p>Use <code>playwright-core<\/code> when you want Nuxt test-utils to drive Playwright through its own e2e utilities. If your project uses Playwright\u2019s standalone test runner instead, Nuxt also supports <code>@playwright\/test<\/code>, but that is a separate setup path.<\/p>\n<p>Next, create a Vitest config. The following example organizes tests into <code>unit<\/code>, <code>nuxt<\/code>, and <code>e2e<\/code> projects:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ vitest.config.mts\nimport vue from '@vitejs\/plugin-vue'\nimport { defineVitestProject } from '@nuxt\/test-utils\/config'\nimport { defineConfig } from 'vitest\/config'\n\nexport default defineConfig({\n  test: {\n    projects: [\n      {\n        plugins: [vue()],\n        test: {\n          name: 'unit',\n          include: ['test\/unit\/**\/*.test.ts'],\n          environment: 'happy-dom',\n        },\n      },\n      {\n        test: {\n          name: 'e2e',\n          include: ['test\/e2e\/**\/*.test.ts'],\n          environment: 'node',\n        },\n      },\n      await defineVitestProject({\n        test: {\n          name: 'nuxt',\n          include: ['app\/**\/*.test.ts', 'test\/nuxt\/**\/*.test.ts'],\n          environment: 'nuxt',\n        },\n      }),\n    ],\n  },\n})<\/pre>\n<p>This is not the only valid structure. You can also colocate test files next to the components they test and route them into the correct Vitest project through file globs. What matters is that tests using Nuxt runtime utilities run in the <code>nuxt<\/code> environment, while e2e tests using <code>@nuxt\/test-utils\/e2e<\/code> run in <code>node<\/code>.<\/p>\n<h2 id=\"tier-1-unit-tests-happy-dom\">Tier 1: Unit tests in <code>happy-dom<\/code><\/h2>\n<p>Most projects should have many unit tests because they are fast, isolated, and easy to run on every code change. Use them for logic that does not require Nuxt context:<\/p>\n<ul>\n<li>Pure utility functions<\/li>\n<li>Simple presentational components<\/li>\n<li>Pure composables<\/li>\n<li>Store logic that can be tested without Nuxt plugins<\/li>\n<\/ul>\n<p>In the companion project, unit tests live in <code>test\/unit\/<\/code> and run in the <code>happy-dom<\/code> environment. The following <code>Headline.test.ts<\/code> file renders a simple Vue component without any Nuxt runtime:<\/p>\n<pre class=\"language-typescript hljs\">import { render, screen } from '@testing-library\/vue'\nimport Headline from '..\/..\/app\/components\/Headline.vue'\n\ndescribe('Headline', () =&gt; {\n  it('renders the provided text', () =&gt; {\n    render(Headline, {\n      props: { text: 'todos' },\n    })\n\n    expect(screen.getByText('todos')).toBeDefined()\n  })\n})<\/pre>\n<p>This test does not need the <code>nuxt<\/code> environment because it only verifies that a Vue component renders a prop. There is no <code>NuxtLink<\/code>, <code>useFetch<\/code>, auto-imported Nuxt composable, route middleware, or plugin dependency involved.<\/p>\n<p>For more unit testing examples, see my previous guide to <a href=\"https:\/\/blog.logrocket.com\/advanced-guide-vitest-testing-mocking\/\">Vitest testing and mocking<\/a>.<\/p>\n<h2 id=\"tier-2-nuxt-runtime-tests-nuxt-environment\">Tier 2: Nuxt runtime tests in the <code>nuxt<\/code> environment<\/h2>\n<p>Use the <code>nuxt<\/code> Vitest environment when the component or page depends on Nuxt-specific behavior. This gives your test access to a Nuxt runtime, including auto-imports, Nuxt modules, routing, and runtime test utilities.<\/p>\n<h3 id=\"use-mountsuspended-components-need-nuxt-context\">Use <code>mountSuspended<\/code> for components that need Nuxt context<\/h3>\n<p><code>mountSuspended<\/code>, provided by <code>@nuxt\/test-utils\/runtime<\/code>, wraps <code>mount<\/code> from <code>@vue\/test-utils<\/code> and mounts a component inside the Nuxt environment. Use it when a component depends on async setup, Nuxt plugins, Nuxt injections, or routing behavior.<\/p>\n<p>The following <code>TodoItem.test.ts<\/code> example verifies that a todo item renders a <code>NuxtLink<\/code> to the correct detail route:<\/p>\n<pre class=\"language-typescript hljs\">import { mountSuspended } from '@nuxt\/test-utils\/runtime'\nimport TodoItem from '~\/components\/TodoItem.vue'\n\nconst mockTodo = {\n  id: 42,\n  label: 'Buy groceries',\n  date: '2026-02-13',\n  checked: false,\n}\n\ndescribe('TodoItem', () =&gt; {\n  it('renders a NuxtLink pointing to \/todos\/:id', async () =&gt; {\n    const wrapper = await mountSuspended(TodoItem, {\n      props: { todo: mockTodo },\n    })\n\n    const link = wrapper.findComponent({ name: 'NuxtLink' })\n    expect(link.exists()).toBe(true)\n    expect(link.props('to')).toBe('\/todos\/42')\n  })\n})<\/pre>\n<p>You could test this with plain <code>mount<\/code>, but you would need to wire up the router manually:<\/p>\n<pre class=\"language-typescript hljs\">it('using mount() requires much more manual setup', () =&gt; {\n  const router = createRouter({\n    history: createMemoryHistory(),\n    routes: [\n      { path: '\/todos\/:id', component: { template: '&lt;div \/&gt;' } },\n      { path: '\/:pathMatch(.*)*', component: { template: '&lt;div \/&gt;' } },\n    ],\n  })\n\n  const wrapper = mount(TodoItem, {\n    props: { todo: mockTodo },\n    global: { plugins: [router] },\n  })\n\n  const link = wrapper.findComponent({ name: 'NuxtLink' })\n  expect(link.exists()).toBe(true)\n  expect(link.props('to')).toBe('\/todos\/42')\n  expect(link.attributes('href')).toBe('\/todos\/42')\n})<\/pre>\n<p>The second test is not wrong, but it obscures the intent. The test is about the component\u2019s route output, not about manually creating a router. <code>mountSuspended<\/code> keeps the setup closer to how the component actually runs in Nuxt.<\/p>\n<h3 id=\"mock-nuxt-server-routes-registerendpoint\">Mock Nuxt server routes with <code>registerEndpoint<\/code><\/h3>\n<p>Nuxt\u2019s data-fetching APIs require a different mocking strategy than ordinary browser <code>fetch<\/code> calls.<\/p>\n<p>The root page in the demo app fetches todos with <code>useFetch<\/code>:<\/p>\n<pre class=\"language-typescript hljs\">const { data } = await useFetch&lt;Todo[]&gt;('\/api\/todos')<\/pre>\n<p>In Nuxt, <code>useFetch<\/code> and <code>$fetch<\/code> do not always call the global browser <code>fetch<\/code> API directly. During SSR, Nuxt can resolve routes like <code>\/api\/todos<\/code> through Nitro server handlers, bypassing HTTP. That means mocking global <code>fetch<\/code> may not affect a <code>useFetch<\/code> call.<\/p>\n<p>Use <code>registerEndpoint<\/code> to mock Nuxt server API routes inside the Nuxt runtime:<\/p>\n<pre class=\"language-typescript hljs\">import { registerEndpoint, renderSuspended } from '@nuxt\/test-utils\/runtime'\nimport { screen } from '@testing-library\/vue'\nimport IndexPage from '~\/pages\/index.vue'\n\nconst mockTodos = [\n  { id: 1, label: 'Buy groceries', date: '2026-02-11', checked: false },\n  { id: 2, label: 'Walk the dog', date: '2026-02-10', checked: true },\n  { id: 3, label: 'Write tests', date: '2026-02-09', checked: false },\n]\n\nregisterEndpoint('\/api\/todos', () =&gt; mockTodos)\n\ndescribe('Index Page', () =&gt; {\n  it('renders todos from the mocked \/api\/todos endpoint', async () =&gt; {\n    await renderSuspended(IndexPage)\n\n    expect(screen.getByText('Buy groceries')).toBeDefined()\n    expect(screen.getByText('Walk the dog')).toBeDefined()\n    expect(screen.getByText('Write tests')).toBeDefined()\n  })\n})<\/pre>\n<p><code>registerEndpoint<\/code> is an API function, not a macro. The first argument is the route, and the second argument returns the response that the Nuxt data-fetching call should receive.<\/p>\n<p>Because this example uses <code>@testing-library\/vue<\/code>, it uses <code>renderSuspended<\/code>, which plays the same role as <code>mountSuspended<\/code> but follows Testing Library\u2019s rendering model.<\/p>\n<h3 id=\"stub-nuxt-components-mockcomponent\">Stub Nuxt components with <code>mockComponent<\/code><\/h3>\n<p>Sometimes you want to test a component that renders another nontrivial child component. In the demo app, <code>TodoList<\/code> renders multiple <code>TodoItem<\/code> components. To focus the test on the list behavior, we can replace <code>TodoItem<\/code> with a small stub.<\/p>\n<p><code>@nuxt\/test-utils<\/code> provides <code>mockComponent<\/code> for this:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ TodoList.test.ts\nimport { mockComponent, renderSuspended } from '@nuxt\/test-utils\/runtime'\nimport { screen } from '@testing-library\/vue'\nimport { defineComponent, h } from 'vue'\nimport TodoList from '~\/components\/TodoList.vue'\nimport { useTodosStore } from '~\/stores\/todos'\n\nconst mockTodos = [\n  { id: 1, label: 'Buy groceries', date: '2026-02-11', checked: false },\n  { id: 2, label: 'Walk the dog', date: '2026-02-10', checked: true },\n  { id: 3, label: 'Write tests', date: '2026-02-09', checked: false },\n]\n\nmockComponent('~\/components\/TodoItem.vue', () =&gt;\n  defineComponent({\n    props: { todo: Object },\n    setup(props) {\n      return () =&gt;\n        h('div', {\n          'data-testid': 'todo-item-stub',\n          'todo-label': props.todo?.label,\n        })\n    },\n  })\n)\n\ndescribe('TodoList', () =&gt; {\n  it('renders one stub per todo seeded into the store', async () =&gt; {\n    const store = useTodosStore()\n    store.setTodos(mockTodos)\n\n    await renderSuspended(TodoList)\n\n    const stubs = screen.getAllByTestId('todo-item-stub')\n    expect(stubs).toMatchSnapshot()\n  })\n})<\/pre>\n<p>The <code>TodoList<\/code> component reads from the Pinia store, so the test seeds the store before rendering the component.<\/p>\n<p>The render-function version above is explicit, but it can be hard to read. Because the Nuxt test environment includes Vue\u2019s runtime template compiler, you can write the stub more simply with a template:<\/p>\n<pre class=\"language-typescript hljs\">mockComponent('~\/components\/TodoItem.vue', {\n  props: ['todo'],\n  template: '&lt;div data-testid=\"todo-item-stub\" :todo-label=\"todo?.label\" \/&gt;',\n})<\/pre>\n<p>One caveat: <code>mockComponent<\/code> is a macro that is hoisted to the top of the file. In practice, that means it is best for file-level stubs. If you need a different stub per test, use a global stub through <code>@vue\/test-utils<\/code> instead:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ TodoList.globalstub.test.ts\ntest('renders one stub per todo seeded into the store', async () =&gt; {\n  const store = useTodosStore()\n  store.setTodos(mockTodos)\n\n  await renderSuspended(TodoList, {\n    global: {\n      stubs: {\n        TodoItem: {\n          props: ['todo'],\n          template: '&lt;div data-testid=\"todo-item-stub\" :todo-label=\"todo?.label\" \/&gt;',\n        },\n      },\n    },\n  })\n\n  const stubs = screen.getAllByTestId('todo-item-stub')\n  expect(stubs).toMatchSnapshot()\n})<\/pre>\n<p>A good rule of thumb: use <code>mockComponent<\/code> when one file-level stub is enough, and use global stubs when each test needs more flexibility.<\/p>\n<h3 id=\"mock-auto-imports-mocknuxtimport\">Mock auto-imports with <code>mockNuxtImport<\/code><\/h3>\n<p>Nuxt auto-imports composables like <code>useRuntimeConfig<\/code>, <code>useState<\/code>, and <code>useHead<\/code>. When you need to replace one of those imports in a test, use <code>mockNuxtImport<\/code>.<\/p>\n<p>For example, the default layout reads the app title from runtime config:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ layouts\/default.vue\nconst { appTitle } = useRuntimeConfig().public<\/pre>\n<p>To test this layout, mock <code>useRuntimeConfig<\/code> and return a custom value:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ DefaultLayout.test.ts\nimport { mockNuxtImport, renderSuspended } from '@nuxt\/test-utils\/runtime'\nimport { screen } from '@testing-library\/vue'\nimport DefaultLayout from '~\/layouts\/default.vue'\n\nmockNuxtImport('useRuntimeConfig', () =&gt; {\n  return () =&gt; ({\n    app: { baseURL: '\/' },\n    public: {\n      appTitle: 'My Custom Title',\n    },\n  })\n})\n\ndescribe('Default Layout', () =&gt; {\n  test('renders the appTitle from useRuntimeConfig in the headline', async () =&gt; {\n    await renderSuspended(DefaultLayout)\n\n    expect(screen.getByText('My Custom Title')).toBeDefined()\n  })\n})<\/pre>\n<p><code>mockNuxtImport<\/code> can only be used once per imported symbol per test file because it is transformed into a hoisted <code>vi.mock<\/code>. When you need to reuse the same mock with different values, pair it with <code>vi.hoisted<\/code>.<\/p>\n<p>Consider this <code>useDarkMode<\/code> composable, which relies on Nuxt\u2019s auto-imported <code>useState<\/code>:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ app\/composables\/useDarkMode.ts\nimport themeConfig from '@\/utils\/theme'\n\nexport const useDarkMode = () =&gt; {\n  const isDark = useState('darkMode', () =&gt; true)\n  const theme = computed(() =&gt;\n    isDark.value ? themeConfig.DARK : themeConfig.LIGHT,\n  )\n\n  function toggleDarkMode() {\n    isDark.value = !isDark.value\n  }\n\n  return { isDark, theme, toggleDarkMode }\n}<\/pre>\n<p>In the test, create one hoisted mock function and set its return value inside each test:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ useDarkMode.test.ts\nimport { mockNuxtImport } from '@nuxt\/test-utils\/runtime'\nimport { ref } from 'vue'\nimport themeConfig from '~\/utils\/theme'\nimport { useDarkMode } from '~\/composables\/useDarkMode'\n\nconst { useStateMock } = vi.hoisted(() =&gt; ({ useStateMock: vi.fn() }))\n\nmockNuxtImport('useState', () =&gt; useStateMock)\n\ndescribe('useDarkMode', () =&gt; {\n  test('starts in dark mode and toggles to light', () =&gt; {\n    useStateMock.mockReturnValue(ref(true))\n\n    const { isDark, theme, toggleDarkMode } = useDarkMode()\n\n    expect(isDark.value).toBe(true)\n    expect(theme.value).toEqual(themeConfig.DARK)\n\n    toggleDarkMode()\n\n    expect(isDark.value).toBe(false)\n    expect(theme.value).toEqual(themeConfig.LIGHT)\n  })\n\n  test('starts in light mode and toggles to dark', () =&gt; {\n    useStateMock.mockReturnValue(ref(false))\n\n    const { isDark, theme, toggleDarkMode } = useDarkMode()\n\n    expect(isDark.value).toBe(false)\n    expect(theme.value).toEqual(themeConfig.LIGHT)\n\n    toggleDarkMode()\n\n    expect(isDark.value).toBe(true)\n    expect(theme.value).toEqual(themeConfig.DARK)\n  })\n})<\/pre>\n<p>The important pattern is this pair:<\/p>\n<pre class=\"language-typescript hljs\">const { useStateMock } = vi.hoisted(() =&gt; ({ useStateMock: vi.fn() }))\n\nmockNuxtImport('useState', () =&gt; useStateMock)<\/pre>\n<p>That gives you one hoisted mock that each test can configure independently.<\/p>\n<h3 id=\"test-dynamic-routes-nuxt-middleware\">Test dynamic routes and Nuxt middleware<\/h3>\n<p>Dynamic routes are a common source of integration bugs in Nuxt because they combine several framework features: route params, middleware, server fetching, error handling, and head management.<\/p>\n<p>The demo project has a todo detail page at <code>pages\/todos\/[id].vue<\/code>:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ pages\/todos\/[id].vue\nimport type { Todo } from '@\/stores\/todos'\n\ndefinePageMeta({\n  middleware: ['validate-todo-id'] as const,\n})\n\nconst route = useRoute()\nconst todoId = Number(route.params.id)\n\nconst { data: todo, error: fetchError } = await useFetch&lt;Todo&gt;(`\/api\/todos\/${todoId}`)\n\nif (fetchError.value) {\n  throw fetchError.value\n}\n\nuseHead({\n  title: `Todo: ${todo.value?.label}`,\n})<\/pre>\n<p>This page uses multiple Nuxt features:<\/p>\n<ul>\n<li><code>definePageMeta<\/code> to attach route middleware<\/li>\n<li><code>useRoute<\/code> to read the dynamic <code>id<\/code> parameter<\/li>\n<li><code>useFetch<\/code> to load a todo from a Nitro server route<\/li>\n<li><code>useHead<\/code> to set the page title<\/li>\n<li>Nuxt error handling for invalid or missing todos<\/li>\n<\/ul>\n<p>Start with the happy path. The test registers a mock server endpoint, renders the page at <code>\/todos\/42<\/code>, and verifies both the visible content and <code>useHead<\/code> call:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ DetailPageHappyPath.test.ts\nimport { mockNuxtImport, registerEndpoint, renderSuspended } from '@nuxt\/test-utils\/runtime'\nimport { screen } from '@testing-library\/vue'\nimport { describe, expect, test, vi } from 'vitest'\nimport type { Todo } from '~\/stores\/todos'\nimport DetailPage from '~\/pages\/todos\/[id].vue'\n\nconst mockTodo: Todo = {\n  id: 42,\n  label: 'Buy groceries',\n  date: '2026-03-26',\n  checked: false,\n}\n\nregisterEndpoint('\/api\/todos\/42', () =&gt; mockTodo)\n\nconst { useHeadMock } = vi.hoisted(() =&gt; ({ useHeadMock: vi.fn() }))\n\nmockNuxtImport('useHead', () =&gt; useHeadMock)\n\ndescribe('Detail Page - happy path', () =&gt; {\n  test('renders the todo title and date and sets the title correctly', async () =&gt; {\n    await renderSuspended(DetailPage, { route: '\/todos\/42' })\n\n    expect(screen.getByText('Buy groceries')).toBeDefined()\n    expect(screen.getByText(\/2026-03-26\/)).toBeDefined()\n    expect(useHeadMock).toHaveBeenCalledWith({ title: 'Todo: Buy groceries' })\n  })\n})<\/pre>\n<p>For good coverage, also test the error paths. If the route contains a valid integer but the server route has no matching todo, the page should throw a 404:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ DetailPageErrorCase.test.ts\nimport { registerEndpoint, renderSuspended } from '@nuxt\/test-utils\/runtime'\nimport { screen } from '@testing-library\/vue'\nimport type { NuxtError } from '#app'\nimport DetailPage from '~\/pages\/todos\/[id].vue'\nimport ErrorPage from '~\/error.vue'\n\nregisterEndpoint('\/api\/todos\/999', () =&gt; {\n  throw createError({\n    statusCode: 404,\n    statusMessage: 'Todo with id 999 not found',\n  })\n})\n\ntest('throws a 404 when the todo does not exist', async () =&gt; {\n  await expect(\n    renderSuspended(DetailPage, {\n      route: '\/todos\/999',\n    }),\n  ).rejects.toMatchObject({\n    statusCode: 404,\n    statusMessage: 'Todo with id 999 not found',\n  })\n})<\/pre>\n<p>If the route parameter is not a valid integer, the middleware should throw a 400:<\/p>\n<pre class=\"language-typescript hljs\">test('throws a 400 when the todo id is not a valid integer', async () =&gt; {\n  await expect(\n    renderSuspended(DetailPage, { route: '\/todos\/abc' }),\n  ).rejects.toMatchObject({\n    statusCode: 400,\n    statusMessage: 'Invalid todo id: \"abc\"',\n  })\n})<\/pre>\n<p>You can also test the error page itself:<\/p>\n<pre class=\"language-typescript hljs\">test('renders status code, message, and back to home button', async () =&gt; {\n  await renderSuspended(ErrorPage, {\n    props: {\n      error: { status: 500, message: 'Something went wrong' } as NuxtError,\n    },\n  })\n\n  expect(screen.getByText('500')).toBeDefined()\n  expect(screen.getByText('Something went wrong')).toBeDefined()\n  expect(screen.getByText('\u2190 Back to home')).toBeDefined()\n})<\/pre>\n<h3 id=\"test-nuxt-middleware-isolation\">Test Nuxt middleware in isolation<\/h3>\n<p>The previous examples test middleware through a real page render. That gives you confidence that the middleware is wired into the route correctly. Sometimes, though, you only want to test the middleware logic quickly.<\/p>\n<p>For that, import the middleware directly and call it with minimal fake route objects.<\/p>\n<p>Here is the middleware implementation:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ app\/middleware\/validate-todo-id.ts\nexport default defineNuxtRouteMiddleware((to) =&gt; {\n  const id = to.params.id as string\n\n  if (!\/^\\d+$\/.test(id)) {\n    abortNavigation(\n      createError({\n        statusCode: 400,\n        statusMessage: `Invalid todo id: \"${id}\"`,\n      }),\n    )\n  }\n})<\/pre>\n<p>The test mocks <code>abortNavigation<\/code> and <code>createError<\/code>, creates fake route objects, and verifies both the allowed and rejected cases:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ ValidateTodoIdMiddleware.test.ts\nimport { mockNuxtImport } from '@nuxt\/test-utils\/runtime'\nimport type { RouteLocationNormalized } from 'vue-router'\nimport validateTodoId from '~\/middleware\/validate-todo-id'\n\nconst { abortNavigationMock, createErrorMock } = vi.hoisted(() =&gt; {\n  return {\n    abortNavigationMock: vi.fn(),\n    createErrorMock: vi.fn((err) =&gt; err),\n  }\n})\n\nmockNuxtImport('abortNavigation', () =&gt; abortNavigationMock)\nmockNuxtImport('createError', () =&gt; createErrorMock)\n\nfunction fakeRoute(id: string): RouteLocationNormalized {\n  return { params: { id } } as unknown as RouteLocationNormalized\n}\n\nconst homeRoute = {\n  name: 'index',\n  path: '\/',\n  fullPath: '\/',\n} as RouteLocationNormalized\n\ndescribe('validate-todo-id middleware', () =&gt; {\n  beforeEach(() =&gt; {\n    vi.clearAllMocks()\n  })\n\n  test('allows navigation for a valid numeric id', () =&gt; {\n    validateTodoId(fakeRoute('42'), homeRoute)\n\n    expect(abortNavigationMock).not.toHaveBeenCalled()\n  })\n\n  test('aborts navigation for a non-numeric id', () =&gt; {\n    validateTodoId(fakeRoute('abc'), homeRoute)\n\n    expect(createErrorMock).toHaveBeenCalledWith(\n      expect.objectContaining({\n        statusCode: 400,\n        statusMessage: 'Invalid todo id: \"abc\"',\n      }),\n    )\n    expect(abortNavigationMock).toHaveBeenCalled()\n  })\n\n  test('aborts navigation for an empty id', () =&gt; {\n    validateTodoId(fakeRoute(''), homeRoute)\n\n    expect(createErrorMock).toHaveBeenCalledWith(\n      expect.objectContaining({\n        statusCode: 400,\n        statusMessage: 'Invalid todo id: \"\"',\n      }),\n    )\n    expect(abortNavigationMock).toHaveBeenCalled()\n  })\n})<\/pre>\n<p>Use isolated middleware tests for fast validation of route rules. Keep at least one page-level runtime test to confirm the middleware is actually connected to the route.<\/p>\n<h2 id=\"tier-3-end-end-tests-node-environment\">Tier 3: End-to-end tests in the <code>node<\/code> environment<\/h2>\n<p>E2E tests exercise the full Nuxt stack. Unlike <code>happy-dom<\/code> tests, they do not assert against a fake DOM. Unlike <code>nuxt<\/code> environment tests, they can run against a real Nuxt server and, if needed, a real browser.<\/p>\n<p>Use e2e tests for critical flows where SSR, routing, middleware, API handlers, hydration, and browser interaction all need to work together.<\/p>\n<h3 id=\"smoke-test-ssr-output-setup-fetch\">Smoke test SSR output with <code>setup<\/code> and <code>$fetch<\/code><\/h3>\n<p>By the time you reach this tier, you usually leave most mocking behind. You do not use <code>registerEndpoint<\/code> or <code>mockNuxtImport<\/code> because a real Nuxt server starts before the suite runs and tears down afterward.<\/p>\n<p>The simplest e2e smoke test starts the Nuxt server and fetches the SSR-rendered HTML:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ e2e\/app.test.ts\nimport { $fetch, setup } from '@nuxt\/test-utils\/e2e'\n\ndescribe('app', async () =&gt; {\n  await setup()\n\n  test('checks availability of input', async () =&gt; {\n    const html = await $fetch('\/')\n    expect(html).toContain('What needs to be done?')\n  })\n})<\/pre>\n<p>When using <code>setup<\/code>, await it at the top of the <code>describe<\/code> block before any tests run. The server stays up for the duration of the suite and is torn down automatically.<\/p>\n<h3 id=\"test-server-api-routes-fetch-fetch\">Test server API routes with <code>$fetch<\/code> and <code>fetch<\/code><\/h3>\n<p style=\"margin-bottom: 15px;\"><code>@nuxt\/test-utils\/e2e<\/code> provides two similar APIs for server requests:<\/p>\n<table>\n<thead>\n<tr>\n<th>API<\/th>\n<th>Use it when<\/th>\n<th>Return behavior<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>$fetch<\/code><\/td>\n<td>You care about parsed HTML or JSON payloads<\/td>\n<td>Uses <code>ofetch<\/code>, parses responses, and throws on non-2xx status codes<\/td>\n<\/tr>\n<tr>\n<td><code>fetch<\/code><\/td>\n<td>You need the raw response object or status code<\/td>\n<td>Returns a response so you can assert <code>status<\/code>, headers, and body<\/td>\n<\/tr>\n<tr>\n<td><code>createPage<\/code><\/td>\n<td>You need real browser interaction<\/td>\n<td>Creates a Playwright page connected to the running Nuxt server<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p style=\"margin-top: 15px;\">For example, use <code>$fetch<\/code> when you only need the payload:<\/p>\n<pre class=\"language-typescript hljs\">test('fetches the homepage', async () =&gt; {\n  const html = await $fetch&lt;string&gt;('\/')\n\n  expect(html).toContain('&lt;!DOCTYPE html&gt;')\n})\n\ntest('fetches JSON API endpoint', async () =&gt; {\n  const data = await $fetch('\/api\/health')\n\n  expect(data).toEqual({ status: 'ok' })\n})<\/pre>\n<p>Use <code>fetch<\/code> when you need to assert on response status codes:<\/p>\n<pre class=\"language-typescript hljs\">\/\/ e2e\/api.test.ts\nimport { $fetch, fetch, setup } from '@nuxt\/test-utils\/e2e'\nimport type { FetchError } from 'ofetch'\n\ninterface Todo {\n  id: number\n  label: string\n  date: string\n  checked: boolean\n}\n\ndescribe('server API routes', async () =&gt; {\n  await setup()\n\n  test('POST \/api\/todos creates a todo and returns status 201', async () =&gt; {\n    const response = await fetch('\/api\/todos', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\/json' },\n      body: JSON.stringify({ label: 'test todo' }),\n    })\n\n    const todo: Todo = await response.json()\n\n    expect(response.status).toBe(201)\n    expect(todo.label).toBe('test todo')\n    expect(todo.checked).toBe(false)\n\n    const todos = await $fetch&lt;Todo[]&gt;('\/api\/todos')\n    expect(todos.length).toBe(1)\n  })\n\n  test('GET \/api\/todos\/:id returns the todo by id', async () =&gt; {\n    const created = await $fetch&lt;Todo&gt;('\/api\/todos', {\n      method: 'POST',\n      body: { label: 'Fetch by id' },\n    })\n\n    const todo = await $fetch&lt;Todo&gt;(`\/api\/todos\/${created.id}`)\n\n    expect(todo.id).toBe(created.id)\n    expect(todo.label).toBe('Fetch by id')\n  })\n\n  test('GET \/api\/todos\/:id returns 404 for an unknown id', async () =&gt; {\n    const error = await $fetch&lt;never&gt;('\/api\/todos\/0').catch((e: FetchError) =&gt; e)\n\n    expect(error.status).toBe(404)\n  })\n})<\/pre>\n<h3 id=\"test-browser-flows-playwright-createpage\">Test browser flows with Playwright and <code>createPage<\/code><\/h3>\n<p>For real browser interaction, use <code>createPage<\/code> from <code>@nuxt\/test-utils\/e2e<\/code>. This gives you a configured Playwright page connected to the Nuxt test server.<\/p>\n<p>The following test creates a todo item, navigates to its detail page, and returns home:<\/p>\n<pre class=\"language-typescript hljs\">import { createPage, fetch, setup, url } from '@nuxt\/test-utils\/e2e'\nimport { beforeEach, describe, expect, test } from 'vitest'\n\ndescribe('navigation flow', async () =&gt; {\n  await setup({ browser: true })\n\n  beforeEach(async () =&gt; {\n    await fetch('\/api\/todos', {\n      method: 'PATCH',\n      body: JSON.stringify({ checked: true }),\n      headers: { 'Content-Type': 'application\/json' },\n    })\n\n    await fetch('\/api\/todos', { method: 'DELETE' })\n  })\n\n  test('adds a todo, navigates to detail page, then back home via headline', async () =&gt; {\n    const page = await createPage()\n    await page.goto(url('\/'), { waitUntil: 'domcontentloaded' })\n\n    const todoLabel = 'nav test'\n    await page.fill('.todo-input input', todoLabel)\n    await page.keyboard.press('Enter')\n\n    const itemLink = page.locator('.item-label', { hasText: todoLabel })\n    await itemLink.click()\n\n    await page.waitForSelector('.todo-detail__title')\n    const titleText = await page.locator('.todo-detail__title').textContent()\n    expect(titleText?.trim()).toBe(todoLabel)\n\n    await page.locator('.layout-headline-link').click()\n    await page.waitForSelector('.todo-input input', { state: 'visible' })\n  })\n})<\/pre>\n<p>This test writes data, so it must clean up between runs. The example checks existing todos and deletes them before each test to keep the test isolated.<\/p>\n<p>If you want to debug in a visible browser, set <code>headless<\/code> to <code>false<\/code>:<\/p>\n<pre class=\"language-typescript hljs\">await setup({\n  browser: true,\n  browserOptions: {\n    type: 'chromium',\n    launch: { headless: false, slowMo: 600 },\n  },\n})<\/pre>\n<p>Nuxt\u2019s Playwright integration also supports hydration-aware navigation. For example:<\/p>\n<pre class=\"language-typescript hljs\">await page.goto(url('\/'), { waitUntil: 'commit' })\nawait page.goto(url('\/'), { waitUntil: 'hydration' })<\/pre>\n<p>This is useful when debugging hydration mismatches. A common example is locale-sensitive formatting: the server renders a date or currency value using the Node.js locale, while the browser formats it using the user\u2019s system locale. The SSR HTML and client output differ, causing Vue to patch the DOM and report a hydration mismatch.<\/p>\n<h2 id=\"common-nuxt-testing-mistakes\">Common Nuxt testing mistakes<\/h2>\n<p style=\"margin-bottom: 15px;\">Here are the most common mistakes to watch for when building a Nuxt test suite:<\/p>\n<table>\n<thead>\n<tr>\n<th>Mistake<\/th>\n<th>Why it causes problems<\/th>\n<th>Better approach<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Mocking global <code>fetch<\/code> for a <code>useFetch<\/code> page<\/td>\n<td>Nuxt may resolve server routes through Nitro rather than browser <code>fetch<\/code><\/td>\n<td>Use <code>registerEndpoint<\/code> in the <code>nuxt<\/code> environment<\/td>\n<\/tr>\n<tr>\n<td>Using <code>mount<\/code> for Nuxt-dependent components<\/td>\n<td>Routing, plugins, and async setup may not be initialized<\/td>\n<td>Use <code>mountSuspended<\/code> or <code>renderSuspended<\/code><\/td>\n<\/tr>\n<tr>\n<td>Treating <code>mockNuxtImport<\/code> like a normal per-test mock<\/td>\n<td>It is hoisted, so multiple implementations in one file can be tricky<\/td>\n<td>Use <code>vi.hoisted<\/code> or split tests into separate files<\/td>\n<\/tr>\n<tr>\n<td>Forgetting to clear mocks<\/td>\n<td>Call history leaks between tests<\/td>\n<td>Run <code>vi.clearAllMocks()<\/code> in <code>beforeEach<\/code><\/td>\n<\/tr>\n<tr>\n<td>Overusing e2e tests<\/td>\n<td>They are slower and harder to maintain<\/td>\n<td>Use unit or Nuxt runtime tests when they provide enough confidence<\/td>\n<\/tr>\n<tr>\n<td>Mixing runtime and e2e utilities in one file<\/td>\n<td>They require different Vitest environments<\/td>\n<td>Keep runtime and e2e tests in separate files<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>Nuxt ships with <code>@nuxt\/test-utils<\/code>, which builds on Vue Test Utils and provides APIs for testing Nuxt-specific behavior like auto-imports, routing, SSR, and server routes.<\/p>\n<p>This guide covered three testing tiers:<\/p>\n<ul>\n<li><strong>Unit tests<\/strong> verify components, composables, and pure logic in isolation<\/li>\n<li style=\"padding-bottom: 10px;\"><strong>Nuxt runtime tests<\/strong> verify Nuxt-specific wiring, including routing, middleware, modules, and data fetching<\/li>\n<li style=\"padding-bottom: 10px;\"><strong>E2E tests<\/strong> verify the full app flow from the user\u2019s perspective, including SSR, hydration, navigation, and server APIs<\/li>\n<\/ul>\n<p>As a rule of thumb, start with the lowest tier that gives you sufficient confidence. Use unit tests for pure logic, Nuxt runtime tests when framework behavior matters, and e2e tests for critical flows where the whole stack needs to work together.<\/p>\n<p>That balance gives you a Nuxt test suite that is fast enough to run often, realistic enough to catch integration bugs, and focused enough to maintain as your application grows.<\/p>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to test Nuxt apps with Vitest, @nuxt\/test-utils, runtime mocks, server route mocks, and Playwright e2e tests.<\/p>\n","protected":false},"author":156415499,"featured_media":213923,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2147999,1],"tags":[2109720],"class_list":["post-213919","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dev","category-uncategorized","tag-nuxtjs"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.1.1 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>An advanced guide to Nuxt testing and mocking - LogRocket Blog<\/title>\n<meta name=\"description\" content=\"Learn how to test Nuxt apps with Vitest, @nuxt\/test-utils, runtime mocks, server route mocks, and Playwright e2e tests.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"An advanced guide to Nuxt testing and mocking - LogRocket Blog\" \/>\n<meta property=\"og:description\" content=\"Learn how to test Nuxt apps with Vitest, @nuxt\/test-utils, runtime mocks, server route mocks, and Playwright e2e tests.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/\" \/>\n<meta property=\"og:site_name\" content=\"LogRocket Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-05T13:00:21+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-05T23:48:04+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2026\/06\/Build-a-headless-table-engine-in-Vue-3-1.png\" \/>\n\t<meta property=\"og:image:width\" content=\"895\" \/>\n\t<meta property=\"og:image:height\" content=\"597\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Sebastian Weber\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@https:\/\/twitter.com\/doppelmutzi\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Sebastian Weber\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/\",\"url\":\"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/\",\"name\":\"An advanced guide to Nuxt testing and mocking - LogRocket Blog\",\"isPartOf\":{\"@id\":\"https:\/\/blog.logrocket.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2026\/06\/Build-a-headless-table-engine-in-Vue-3-1.png\",\"datePublished\":\"2026-06-05T13:00:21+00:00\",\"dateModified\":\"2026-06-05T23:48:04+00:00\",\"author\":{\"@id\":\"https:\/\/blog.logrocket.com\/#\/schema\/person\/72318fdcd9f62158f7f2cdc66f0b8ff4\"},\"description\":\"Learn how to test Nuxt apps with Vitest, @nuxt\/test-utils, runtime mocks, server route mocks, and Playwright e2e tests.\",\"breadcrumb\":{\"@id\":\"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/#primaryimage\",\"url\":\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2026\/06\/Build-a-headless-table-engine-in-Vue-3-1.png\",\"contentUrl\":\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2026\/06\/Build-a-headless-table-engine-in-Vue-3-1.png\",\"width\":895,\"height\":597,\"caption\":\"An advanced guide to Nuxt testing and mocking\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/blog.logrocket.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"An advanced guide to Nuxt testing and mocking\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/blog.logrocket.com\/#website\",\"url\":\"https:\/\/blog.logrocket.com\/\",\"name\":\"LogRocket Blog\",\"description\":\"Resources to Help Product Teams Ship Amazing Digital Experiences\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/blog.logrocket.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/blog.logrocket.com\/#\/schema\/person\/72318fdcd9f62158f7f2cdc66f0b8ff4\",\"name\":\"Sebastian Weber\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.logrocket.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/f7879d811d19d59de80ae57dcea12a17c65f6372338812f4186a3e69be305b5a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/f7879d811d19d59de80ae57dcea12a17c65f6372338812f4186a3e69be305b5a?s=96&d=mm&r=g\",\"caption\":\"Sebastian Weber\"},\"description\":\"Fell in love with CSS and JS over 20 years ago.\",\"sameAs\":[\"https:\/\/doppelmutzi.github.io\/\",\"https:\/\/x.com\/https:\/\/twitter.com\/doppelmutzi\"],\"url\":\"https:\/\/blog.logrocket.com\/author\/sebastianweber\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"An advanced guide to Nuxt testing and mocking - LogRocket Blog","description":"Learn how to test Nuxt apps with Vitest, @nuxt\/test-utils, runtime mocks, server route mocks, and Playwright e2e tests.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/","og_locale":"en_US","og_type":"article","og_title":"An advanced guide to Nuxt testing and mocking - LogRocket Blog","og_description":"Learn how to test Nuxt apps with Vitest, @nuxt\/test-utils, runtime mocks, server route mocks, and Playwright e2e tests.","og_url":"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/","og_site_name":"LogRocket Blog","article_published_time":"2026-06-05T13:00:21+00:00","article_modified_time":"2026-06-05T23:48:04+00:00","og_image":[{"width":895,"height":597,"url":"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2026\/06\/Build-a-headless-table-engine-in-Vue-3-1.png","type":"image\/png"}],"author":"Sebastian Weber","twitter_card":"summary_large_image","twitter_creator":"@https:\/\/twitter.com\/doppelmutzi","twitter_misc":{"Written by":"Sebastian Weber","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/","url":"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/","name":"An advanced guide to Nuxt testing and mocking - LogRocket Blog","isPartOf":{"@id":"https:\/\/blog.logrocket.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/#primaryimage"},"image":{"@id":"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/#primaryimage"},"thumbnailUrl":"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2026\/06\/Build-a-headless-table-engine-in-Vue-3-1.png","datePublished":"2026-06-05T13:00:21+00:00","dateModified":"2026-06-05T23:48:04+00:00","author":{"@id":"https:\/\/blog.logrocket.com\/#\/schema\/person\/72318fdcd9f62158f7f2cdc66f0b8ff4"},"description":"Learn how to test Nuxt apps with Vitest, @nuxt\/test-utils, runtime mocks, server route mocks, and Playwright e2e tests.","breadcrumb":{"@id":"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/#primaryimage","url":"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2026\/06\/Build-a-headless-table-engine-in-Vue-3-1.png","contentUrl":"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2026\/06\/Build-a-headless-table-engine-in-Vue-3-1.png","width":895,"height":597,"caption":"An advanced guide to Nuxt testing and mocking"},{"@type":"BreadcrumbList","@id":"https:\/\/blog.logrocket.com\/advanced-guide-nuxt-testing-mocking\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/blog.logrocket.com\/"},{"@type":"ListItem","position":2,"name":"An advanced guide to Nuxt testing and mocking"}]},{"@type":"WebSite","@id":"https:\/\/blog.logrocket.com\/#website","url":"https:\/\/blog.logrocket.com\/","name":"LogRocket Blog","description":"Resources to Help Product Teams Ship Amazing Digital Experiences","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.logrocket.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/blog.logrocket.com\/#\/schema\/person\/72318fdcd9f62158f7f2cdc66f0b8ff4","name":"Sebastian Weber","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.logrocket.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/f7879d811d19d59de80ae57dcea12a17c65f6372338812f4186a3e69be305b5a?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f7879d811d19d59de80ae57dcea12a17c65f6372338812f4186a3e69be305b5a?s=96&d=mm&r=g","caption":"Sebastian Weber"},"description":"Fell in love with CSS and JS over 20 years ago.","sameAs":["https:\/\/doppelmutzi.github.io\/","https:\/\/x.com\/https:\/\/twitter.com\/doppelmutzi"],"url":"https:\/\/blog.logrocket.com\/author\/sebastianweber\/"}]}},"yoast_description":"Learn how to test Nuxt apps with Vitest, @nuxt\/test-utils, runtime mocks, server route mocks, and Playwright e2e tests.","_links":{"self":[{"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/posts\/213919","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/users\/156415499"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/comments?post=213919"}],"version-history":[{"count":3,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/posts\/213919\/revisions"}],"predecessor-version":[{"id":213924,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/posts\/213919\/revisions\/213924"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/media\/213923"}],"wp:attachment":[{"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/media?parent=213919"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/categories?post=213919"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/tags?post=213919"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}