All Articles
Testing

Setting Up Vitest on a Nuxt App With Zero Existing Tests

Shahid MalikBy Shahid MalikSeptember 4, 20265 min read

Tabeer.ai's frontend had no unit tests at all before this. Rather than write a sprawling first test suite, here's the actual config and the one real test that proves the setup works — a foundation to build on, not a coverage number to chase.

Adding a test runner to a frontend that has zero tests is a different problem than adding one to a codebase that already has hundreds — there's no existing suite to run through the new tool, no confirmation that "everything still passes" is even meaningful yet. That's exactly where Tabeer.ai's Nuxt frontend was before this: real production code, real users, zero unit tests. Here's what actually got set up, and why one real test beats a padded-out placeholder suite.

Install

npm install -D vitest @vue/test-utils @nuxt/test-utils happy-dom

Config

// vitest.config.ts
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    environment: 'happy-dom',
    include: ['tests/unit/**/*.spec.ts'],
  },
  resolve: {
    alias: {
      '~': fileURLToPath(new URL('./app', import.meta.url)),
      '@': fileURLToPath(new URL('./app', import.meta.url)),
    },
  },
})

Two decisions worth explaining. happy-dom over jsdom: a lighter, faster DOM implementation for tests that don't need jsdom's more complete (and slower) browser API surface — the right default until a specific test actually needs something happy-dom doesn't implement. A plain vitest.config.ts rather than @nuxt/test-utils's full Nuxt test environment: @nuxt/test-utils spins up an actual Nuxt instance per test file, which is the right tool for testing components that lean on Nuxt-specific runtime features (auto-imports, useFetch, route composables) but meaningfully slower for testing plain logic that doesn't need any of that.

The One Real Test

Not a placeholder — an actual regression test for the exam data every page on the site depends on:

// tests/unit/exams.spec.ts
import { describe, expect, it } from 'vitest'
import { EXAMS } from '~/data/exams'

describe('EXAMS data', () => {
  it('has exactly the 8 exams the platform supports', () => {
    expect(EXAMS).toHaveLength(8)
  })

  it('has a unique code and slug per exam', () => {
    const codes = new Set(EXAMS.map((e) => e.code))
    const slugs = new Set(EXAMS.map((e) => e.slug))
    expect(codes.size).toBe(EXAMS.length)
    expect(slugs.size).toBe(EXAMS.length)
  })

  it('every exam has non-empty labels', () => {
    for (const exam of EXAMS) {
      expect(exam.label.length).toBeGreaterThan(0)
      expect(exam.shortLabel.length).toBeGreaterThan(0)
    }
  })
})
npx vitest run
# Test Files  1 passed (1)
#      Tests  3 passed (3)
#   Duration  241ms

This is a genuinely useful test, not a token one: this exact data array is what every exam page's routing, subject filtering, and personalization logic reads from — a duplicate slug or a typo'd code here would silently break navigation across the entire site, and this catches it in 241ms instead of during a manual click-through.

Why Not Write a Bigger Suite Right Away

The temptation with a from-zero test setup is to write broad coverage immediately — comprehensive component tests, mocked API responses, the works. That's a real, separate undertaking with its own scope, and cramming it into "set up the test runner" produces exactly the kind of rushed, shallow coverage that looks good in a PR diff and catches nothing real six months later. One test that actually protects something real (data integrity every page depends on) is worth more than twenty tests that assert a component renders without checking whether it renders correctly.

What's Actually in Place Now

npm run test:unit runs in CI-friendly mode (vitest run, not watch mode) in under 300ms. The next real test to add is whatever the next bug report turns out to be — write the test that would have caught it, not a test for its own sake.

If you're adding a first test suite to a frontend that's been running in production without one, and want the foundation built right rather than padded for a coverage percentage, get in touch.

Related Topics:NuxtVueVitestTesting

Related Articles

Testing

Adding pytest to a Django Project Without Rewriting a Single Test

Tabeer.ai had 659 tests written the standard Django unittest way. Adding pytest didn't mean migrating them — pytest-django runs unittest-style TestCase classes completely unchanged. Here's the actual config and what 'zero-rewrite' really means in practice.

5 min read
Shahid Malik - AI-First Odoo Consultant

Shahid Malik

AI-First Odoo ERP Specialist

Shahid Malik is an AI-first Odoo consultant helping businesses solve complex ERP and business process challenges. His work combines Odoo consulting, process optimization, automation, integrations, migrations, and practical AI solutions to build scalable and reliable business systems.

Book a consultation for your Odoo project
Discuss Your Odoo Project