[{"data":1,"prerenderedAt":790},["ShallowReactive",2],{"\u002Fblog\u002Fnuxt-test-utils-setup-examples-best-practices-data":3},{"post":4,"surround":779},{"id":5,"title":6,"alternates":7,"authors":8,"badge":14,"body":16,"date":737,"dateModified":738,"description":26,"extension":739,"head":738,"hero_image_url":25,"json_ld":740,"meta":762,"navigation":763,"ogImage":738,"outbound_links":764,"path":765,"primary_keyword":766,"related_articles":767,"robots":738,"schemaOrg":738,"search_intent":768,"seo":769,"sitemap":770,"stem":771,"supporting_keywords":772,"tags":777,"__hash__":778},"blog_en\u002Fblog\u002Fnuxt-test-utils-setup-examples-best-practices.md","Nuxt test-utils: setup, examples, and testing patterns",[],[9],{"name":10,"to":11,"avatar":12},"Tom Han","https:\u002F\u002Fx.com\u002Ftomhan245",{"src":13},"https:\u002F\u002Fcdn.shipahe.ad\u002Ftomhan.webp",{"label":15},"How-to",{"type":17,"value":18,"toc":712},"minimark",[19,28,32,35,40,43,69,76,80,165,168,172,177,187,232,249,253,268,313,319,325,331,386,389,395,398,429,432,436,439,478,481,507,522,526,636,640,665,668],[20,21,22],"figure",{},[23,24],"img",{"src":25,"alt":26,"style":27},"https:\u002F\u002Fshipahe.ad\u002Fimages\u002Fblog\u002Fnuxt-test-utils-setup-examples-best-practices\u002Fpost-744.webp","Set up nuxt test-utils with Vitest, write component, server, and browser tests for Nuxt 3, and avoid flaky suites with clear, concrete patterns.","max-width:100%;border-radius:12px",[29,30,31],"p",{},"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.",[29,33,34],{},"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?",[36,37,39],"h2",{"id":38},"what-nuxt-test-utils-actually-provides","What nuxt test-utils actually provides",[29,41,42],{},"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:",[44,45,46,55,62],"ul",{},[47,48,49,50,54],"li",{},"Mount Vue components with Nuxt plugins and auto-imports available using ",[51,52,53],"code",{},"mountSuspended",". Async setup and Suspense are resolved before assertions.",[47,56,57,58,61],{},"Call server routes with ",[51,59,60],{},"$fetch"," against a live Nitro instance that starts once for your suite.",[47,63,64,65,68],{},"Open a browser page for lightweight end-to-end checks with ",[51,66,67],{},"createPage"," (Playwright under the hood).",[29,70,71,72,75],{},"It complements ",[51,73,74],{},"@vue\u002Ftest-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.",[36,77,79],{"id":78},"set-up-nuxt-test-utils-with-vitest-step-by-step","Set up nuxt test-utils with Vitest (step by step)",[81,82,83,93,115,132,145],"ol",{},[47,84,85,89,90],{},[86,87,88],"strong",{},"Install dev dependencies."," ",[51,91,92],{},"npm i -D vitest nuxt-vitest @nuxt\u002Ftest-utils @vue\u002Ftest-utils",[47,94,95,98,99,102,103,106,107,110,111,114],{},[86,96,97],{},"Enable the Vitest module."," In ",[51,100,101],{},"nuxt.config.ts"," add ",[51,104,105],{},"modules: ['nuxt-vitest']",". If you need test-only config, set ",[51,108,109],{},"test"," keys inside ",[51,112,113],{},"runtimeConfig",".",[47,116,117,98,120,123,124,127,128,131],{},[86,118,119],{},"Create a Vitest config.",[51,121,122],{},"vitest.config.ts",": ",[51,125,126],{},"export default defineConfig({ test: { environment: 'nuxt', globals: true, setupFiles: ['.\u002Ftests\u002Fsetup.ts'] } })",". The setup file is optional but handy for ",[51,129,130],{},"mockNuxtImport"," and test-wide hooks.",[47,133,134,137,138,141,142,114],{},[86,135,136],{},"Add a first test."," Create ",[51,139,140],{},"tests\u002Fcomponents\u002FCounter.spec.ts",". Example: ",[51,143,144],{},"const wrapper = await mountSuspended(Counter); await wrapper.get('button').trigger('click'); expect(wrapper.text()).toContain('1')",[47,146,147,89,150,153,154,157,158,160,161,164],{},[86,148,149],{},"Run tests.",[51,151,152],{},"npx vitest"," for watch mode, or ",[51,155,156],{},"npx vitest --run"," in CI. If you will use ",[51,159,67],{},", run ",[51,162,163],{},"npx playwright install"," once.",[29,166,167],{},"That is enough to mount components with a live Nuxt context and to hit server routes in tests.",[36,169,171],{"id":170},"write-tests-that-cover-real-nuxt-behavior","Write tests that cover real Nuxt behavior",[173,174,176],"h3",{"id":175},"_1-components-mount-with-nuxt-context","1) Components: mount with Nuxt context",[29,178,179,180,182,183,186],{},"Use ",[51,181,53],{}," from ",[51,184,185],{},"@nuxt\u002Ftest-utils\u002Fruntime"," so async setup and Suspense complete before assertions. This avoids race conditions that appear with a plain Vue mount.",[44,188,189,197,209,223],{},[47,190,191,89,194,114],{},[86,192,193],{},"Mount with props.",[51,195,196],{},"const wrapper = await mountSuspended(MyButton, { props: { label: 'Pay now' } })",[47,198,199,89,202,205,206,114],{},[86,200,201],{},"Assert DOM and events.",[51,203,204],{},"await wrapper.get('button').trigger('click')"," then ",[51,207,208],{},"expect(wrapper.emitted('click')).toBeTruthy()",[47,210,211,214,215,218,219,222],{},[86,212,213],{},"Provide plugins or mocks."," Pass ",[51,216,217],{},"global.plugins"," (e.g., your i18n instance) or ",[51,220,221],{},"global.mocks"," for injected keys.",[47,224,225,228,229,114],{},[86,226,227],{},"Router-aware UI."," If the component reads route params, mock them: ",[51,230,231],{},"mockNuxtImport('useRoute', () => () => ({ params: { id: '42' }, query: {} }))",[29,233,234,235,238,239,241,242,244,245,248],{},"Tip: If your component reads ",[51,236,237],{},"useRuntimeConfig()",", do not hand-roll a stub. Prefer setting ",[51,240,113],{}," for the test environment in ",[51,243,101],{},", or mock the import with ",[51,246,247],{},"mockNuxtImport('useRuntimeConfig', ...)"," for a single spec.",[173,250,252],{"id":251},"_2-composables-isolate-logic-mock-nuxt-imports","2) Composables: isolate logic, mock Nuxt imports",[29,254,255,256,259,260,263,264,267],{},"Composables often call ",[51,257,258],{},"useFetch",", ",[51,261,262],{},"useState",", or ",[51,265,266],{},"useRuntimeConfig",". Mock those auto-imports so unit tests never hit the network.",[44,269,270,278,301],{},[47,271,272,89,275,114],{},[86,273,274],{},"Mock a runtime value.",[51,276,277],{},"mockNuxtImport('useRuntimeConfig', () => () => ({ public: { apiBase: '\u002Fapi' } }))",[47,279,280,89,286,289,290,259,293,296,297,300],{},[86,281,282,283,285],{},"Mock ",[51,284,258],{}," with reactive results.",[51,287,288],{},"mockNuxtImport('useFetch', () => async () => ({ data: { value: { ok: true } }, pending: { value: false }, error: { value: null } }))",". Return refs for ",[51,291,292],{},"data",[51,294,295],{},"pending",", and ",[51,298,299],{},"error"," to match real behavior.",[47,302,303,306,307,309,310,312],{},[86,304,305],{},"Exercise branches."," Write one test for success, one for ",[51,308,299],{},", one for ",[51,311,295],{},". Add a case for 401 to confirm your refresh or logout path.",[29,314,315,316,318],{},"Run the composable inside a tiny component with ",[51,317,53],{},", or export pure helpers for direct testing.",[173,320,322,323],{"id":321},"_3-server-routes-call-nitro-with-fetch","3) Server routes: call Nitro with ",[51,324,60],{},[29,326,327,328,330],{},"Start Nitro once at the suite level, then hit endpoints with ",[51,329,60],{},". You get real middleware, auth checks, and runtime config.",[44,332,333,349,358,372],{},[47,334,335,89,338,341,342,345,346,114],{},[86,336,337],{},"Boot the test context.",[51,339,340],{},"import { setup, $fetch } from '@nuxt\u002Ftest-utils'"," and ",[51,343,344],{},"await setup({})"," in ",[51,347,348],{},"tests\u002Fsetup.ts",[47,350,351,89,354,357],{},[86,352,353],{},"Call endpoints.",[51,355,356],{},"const res = await $fetch('\u002Fapi\u002Fhealth')","; assert status and JSON shape.",[47,359,360,363,364,367,368,371],{},[86,361,362],{},"Seed data."," Prepare rows in a test database in ",[51,365,366],{},"beforeEach",", clean up in ",[51,369,370],{},"afterEach",". Use an isolated schema or an in-memory DB to avoid cross-test bleed.",[47,373,374,377,378,381,382,385],{},[86,375,376],{},"Auth headers."," For cookie auth, pass ",[51,379,380],{},"headers: { cookie: 'session=...' }",". For token auth, set ",[51,383,384],{},"authorization: 'Bearer \u003Ctoken>'"," and assert 401\u002F403 on bad tokens.",[29,387,388],{},"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.",[173,390,392,393],{"id":391},"_4-pages-and-middleware-browser-checks-with-createpage","4) Pages and middleware: browser checks with ",[51,394,67],{},[29,396,397],{},"For a quick smoke test, open a page in a real browser context.",[44,399,400,408,420],{},[47,401,402,89,405,114],{},[86,403,404],{},"Enable the browser.",[51,406,407],{},"await setup({ browser: true })",[47,409,410,89,413,416,417,114],{},[86,411,412],{},"Open a page.",[51,414,415],{},"const page = await createPage('\u002F')",", then ",[51,418,419],{},"await page.locator('h1').waitFor()",[47,421,422,425,426,114],{},[86,423,424],{},"Assert UI."," Use stable selectors: ",[51,427,428],{},"await expect(await page.textContent('[data-testid=\"title\"]')).toContain('Dashboard')",[29,430,431],{},"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.",[36,433,435],{"id":434},"fit-testing-into-a-nuxt-saas-workflow","Fit testing into a Nuxt SaaS workflow",[29,437,438],{},"If you start from a Nuxt SaaS starter kit or a Vue\u002FNuxt 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.",[44,440,441,447,453,459,472],{},[47,442,443,446],{},[86,444,445],{},"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.",[47,448,449,452],{},[86,450,451],{},"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).",[47,454,455,458],{},[86,456,457],{},"i18n."," Mount with at least two locales. Assert a few high-value keys render and that the locale switcher updates route and head tags.",[47,460,461,467,468,471],{},[86,462,463,464,114],{},"SEO and ",[51,465,466],{},"useSeoMeta"," For critical pages, assert a non-empty ",[51,469,470],{},"\u003Ctitle>",", canonical URL where applicable, and language meta when locale changes.",[47,473,474,477],{},[86,475,476],{},"Admin roles."," Verify role-based middleware blocks non-admins and hides admin-only UI controls.",[29,479,480],{},"Practical structure that scales:",[44,482,483,489,495,501],{},[47,484,485,488],{},[51,486,487],{},"tests\u002Fcomponents"," for isolated UI",[47,490,491,494],{},[51,492,493],{},"tests\u002Fcomposables"," for logic",[47,496,497,500],{},[51,498,499],{},"tests\u002Fserver"," for Nitro routes and webhooks",[47,502,503,506],{},[51,504,505],{},"tests\u002Fbrowser"," for a handful of smoke tests",[29,508,509,510,513,514,517,518,521],{},"Add tiny helpers that pay off quickly: ",[51,511,512],{},"loginAs(user)"," to set a cookie in tests, ",[51,515,516],{},"factory({ table: 'users' })"," to create rows, and ",[51,519,520],{},"withLocale(locale, fn)"," to swap language during a spec.",[36,523,525],{"id":524},"pitfalls-and-ci-without-flakes","Pitfalls and CI without flakes",[44,527,528,537,552,563,572,584,594,600,618,624],{},[47,529,530,533,534,536],{},[86,531,532],{},"Tests hang on Suspense."," Use ",[51,535,53],{},", await the mount, and avoid triggering async work after assertions.",[47,538,539,542,543,345,546,341,548,345,550,114],{},[86,540,541],{},"Auto-imports not found."," Ensure ",[51,544,545],{},"environment: 'nuxt'",[51,547,122],{},[51,549,105],{},[51,551,101],{},[47,553,554,557,558,560,561,114],{},[86,555,556],{},"Network calls in unit tests."," Mock ",[51,559,258],{}," or your HTTP client. Keep real calls in server route tests with ",[51,562,60],{},[47,564,565,568,569,571],{},[86,566,567],{},"Stale Nitro state."," If a test needs a fresh server, isolate it in its own file or reset state in ",[51,570,370],{},". Prefer stateless handlers.",[47,573,574,577,578,580,581,583],{},[86,575,576],{},"Playwright missing."," If ",[51,579,67],{}," fails, run ",[51,582,163],{}," and rerun the suite.",[47,585,586,589,590,593],{},[86,587,588],{},"ESM\u002FTypeScript hiccups."," Align ",[51,591,592],{},"tsconfig.json"," with Nuxt defaults. Avoid CJS-only libraries in ESM tests.",[47,595,596,599],{},[86,597,598],{},"Pin execution in CI."," Use the same Node version locally and in CI. Cache Playwright browsers to speed up runs.",[47,601,602,605,606,609,610,613,614,617],{},[86,603,604],{},"One command."," Add ",[51,607,608],{},"\"test\": \"vitest --run\""," to ",[51,611,612],{},"package.json",". Add ",[51,615,616],{},"--reporter=junit"," if CI expects JUnit XML.",[47,619,620,623],{},[86,621,622],{},"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.",[47,625,626,629,630,345,633,635],{},[86,627,628],{},"Coverage that matters."," Enable ",[51,631,632],{},"coverage",[51,634,122],{},". Focus on paths that guard money, like signup and checkout, not every branch of a spinner component.",[36,637,639],{"id":638},"key-takeaways","Key takeaways",[44,641,642,645,656,659,662],{},[47,643,644],{},"nuxt test-utils runs a real Nuxt context inside Vitest so your tests mirror production behavior.",[47,646,179,647,649,650,652,653,655],{},[51,648,53],{}," for components, ",[51,651,60],{}," for server routes, and ",[51,654,67],{}," for quick browser checks.",[47,657,658],{},"Mock Nuxt auto-imports in composable tests to avoid network calls and flakes.",[47,660,661],{},"Prioritize auth, protected pages, payments, and locale routing in a Nuxt SaaS template.",[47,663,664],{},"Keep browser tests short; let unit and integration tests carry most coverage.",[29,666,667],{},"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.",[669,670,673,677,681,684,688,691,695,698,702,705,709],"section",{"className":671},[672],"post-faq",[36,674,676],{"id":675},"faq","FAQ",[173,678,680],{"id":679},"what-is-the-difference-between-nuxt-test-utils-and-vuetest-utils","What is the difference between nuxt test-utils and @vue\u002Ftest-utils?",[29,682,683],{},"nuxt test-utils boots a Nuxt-aware test environment and Nitro server. @vue\u002Ftest-utils mounts Vue components. Use them together to test Nuxt components with real context.",[173,685,687],{"id":686},"how-do-i-test-a-nuxt-server-route-with-vitest","How do I test a Nuxt server route with Vitest?",[29,689,690],{},"Call setup from @nuxt\u002Ftest-utils, then use $fetch to hit your endpoint. Seed any required data before the request and assert the response shape.",[173,692,694],{"id":693},"do-i-need-playwright-to-use-createpage","Do I need Playwright to use createPage?",[29,696,697],{},"Yes. createPage relies on Playwright. Install it once with npx playwright install, then you can open real pages in tests.",[173,699,701],{"id":700},"how-can-i-mock-usefetch-in-a-composable-test","How can I mock useFetch in a composable test?",[29,703,704],{},"Use mockNuxtImport from nuxt-vitest\u002Futils to stub useFetch and return a predictable data value, pending state, and error.",[173,706,708],{"id":707},"should-i-still-write-tests-if-i-use-a-nuxt-saas-starter-kit","Should I still write tests if I use a Nuxt SaaS starter kit?",[29,710,711],{},"Yes. A starter kit helps you ship fast, but tests protect key flows like auth, checkout, and i18n when you start changing code.",{"title":713,"searchDepth":714,"depth":714,"links":715},"",2,[716,717,718,727,728,729,730],{"id":38,"depth":714,"text":39},{"id":78,"depth":714,"text":79},{"id":170,"depth":714,"text":171,"children":719},[720,722,723,725],{"id":175,"depth":721,"text":176},3,{"id":251,"depth":721,"text":252},{"id":321,"depth":721,"text":724},"3) Server routes: call Nitro with $fetch",{"id":391,"depth":721,"text":726},"4) Pages and middleware: browser checks with createPage",{"id":434,"depth":714,"text":435},{"id":524,"depth":714,"text":525},{"id":638,"depth":714,"text":639},{"id":675,"depth":714,"text":676,"children":731},[732,733,734,735,736],{"id":679,"depth":721,"text":680},{"id":686,"depth":721,"text":687},{"id":693,"depth":721,"text":694},{"id":700,"depth":721,"text":701},{"id":707,"depth":721,"text":708},"2026-09-18",null,"md",{"@context":741,"@graph":742},"https:\u002F\u002Fschema.org",[743,747],{"@type":744,"headline":6,"description":26,"image":25,"inLanguage":745,"datePublished":746},"BlogPosting","en","2026-09-18T08:00:09.621Z",{"@type":748,"mainEntity":749},"FAQPage",[750,754,756,758,760],{"@type":751,"name":680,"acceptedAnswer":752},"Question",{"@type":753,"text":683},"Answer",{"@type":751,"name":687,"acceptedAnswer":755},{"@type":753,"text":690},{"@type":751,"name":694,"acceptedAnswer":757},{"@type":753,"text":697},{"@type":751,"name":701,"acceptedAnswer":759},{"@type":753,"text":704},{"@type":751,"name":708,"acceptedAnswer":761},{"@type":753,"text":711},{},true,[],"\u002Fblog\u002Fnuxt-test-utils-setup-examples-best-practices","nuxt",[],"Informational",{"title":6,"description":26},{"loc":765},"blog\u002Fnuxt-test-utils-setup-examples-best-practices",[766,773,774,775,776],"testing","vitest","vue 3","saas",[766,773,774,775,776],"loUUWpQ2zKC12A8PM0WU4CjBIgcdCNWFHPmgQP6I778",[780,785],{"title":781,"path":782,"stem":783,"description":784,"children":-1},"Nuxt Stripe payments for one-time and subscription billing","\u002Fblog\u002Fnuxt-stripe-payments-for-one-time-and-subscription-billing","blog\u002Fnuxt-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.",{"title":786,"path":787,"stem":788,"description":789,"children":-1},"nuxt\u002Ftest-utils: Practical testing for Nuxt 3 SaaS apps","\u002Fblog\u002Fnuxt-test-utils-step-by-step-guide-testing-nuxt-3","blog\u002Fnuxt-test-utils-step-by-step-guide-testing-nuxt-3","Set up nuxt\u002Ftest-utils with Vitest to test pages, APIs, auth, i18n, and payments in Nuxt 3. Real examples, CI tips, and pitfalls to ship with confidence.",1789729300596]