Claude Code & Vitest: Blitzschnelle Unit-Tests für JavaScript

Unit-Tests sind das Fundament jedes seriösen JavaScript-Projekts — aber ehrlich gesagt sind sie auch das, was viele Entwickler am liebsten auf später verschieben. Setup, Mocking-Boilerplate, Snapshot-Pflege: der Overhead fühlt sich häufig größer an als der Nutzen. Vitest löst das Setup-Problem, Claude Code löst das Schreib-Problem. Zusammen sinkt die Einstiegsbarriere so weit, dass Tests aufzuschieben keinen guten Grund mehr hat.

Dieser Artikel zeigt, wie du Vitest in einem Vite-Projekt aufsetzt, was vi.mock, vi.fn und vi.spyOn leisten, wie Snapshot-Tests und Coverage funktionieren — und wie Claude Code dir dabei den größten Teil der Arbeit abnimmt.

Claude Code Mastery — Testing, Agents, Hooks auf Deutsch

Nicht nur Vitest: der Kurs zeigt, wie du Claude Code für vollständige Entwicklungsworkflows einsetzt — von der Test-Suite bis zum autonomen Agenten. Einmalig bezahlt, kein Abo.

Zum Kurs — Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht

1. Vitest vs. Jest: Warum der Wechsel sich lohnt

Jest war jahrelang der Standard für JavaScript-Unit-Tests — und ist es in vielen Legacy-Projekten immer noch. Aber wer heute ein neues Projekt mit Vite aufbaut, hat einen starken Grund, direkt zu Vitest zu greifen: kein separater Transform-Layer. Vitest verwendet dieselbe Vite-Pipeline wie dein Entwicklungsserver. Das bedeutet, dass deine vite.config.ts auch für Tests gilt — Aliases, Plugins, Environment-Variablen funktionieren ohne Doppelkonfiguration.

Der zweite Unterschied ist die Geschwindigkeit. Vitest nutzt native ES-Module und führt Tests parallel in Worker-Threads aus. In der Praxis bedeutet das: eine Test-Suite, die mit Jest 8–12 Sekunden braucht, läuft mit Vitest in 1–3 Sekunden. Im Watch-Modus fühlt sich das wie ein anderes Werkzeug an.

Migration von Jest: Die API-Kompatibilität ist hoch — in den meisten Projekten reicht es, jest durch vitest zu ersetzen und die Imports anzupassen. Claude Code erledigt diese Migration zuverlässig: einfach eine bestehende Jest-Testdatei übergeben und um Konvertierung bitten.

2. Setup: vitest.config.ts und erste Tests

Installation in einem Vite-Projekt:

npm install -D vitest @vitest/coverage-v8

Die Konfiguration gehört entweder direkt in vite.config.ts oder in eine separate vitest.config.ts:

// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    environment: 'jsdom',     // oder 'node' für reine Backend-Logik
    globals: true,            // describe/it/expect ohne Import
    setupFiles: ['./src/test/setup.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'lcov'],
      exclude: ['node_modules', 'src/test']
    }
  }
})

Claude Code kann diese Konfiguration für dein Projekt erzeugen — inklusive der richtigen environment-Einstellung, abhängig davon, ob du DOM-Tests oder reine Node-Logik testest:

claude "Erstelle eine vitest.config.ts für ein React-Projekt mit jsdom,
globals, Coverage mit v8 und einem Setup-File für Testing Library"

Erster Test mit describe, it und expect

Die Grundstruktur ist identisch zu Jest:

// src/utils/format.test.ts
import { describe, it, expect } from 'vitest'
import { formatCurrency } from './format'

describe('formatCurrency', () => {
  it('formatiert positive Beträge korrekt', () => {
    expect(formatCurrency(1234.5, 'EUR')).toBe('1.234,50 €')
  })

  it('behandelt null als Null-Betrag', () => {
    expect(formatCurrency(null, 'EUR')).toBe('0,00 €')
  })

  it('wirft bei unbekannter Währung einen Fehler', () => {
    expect(() => formatCurrency(100, 'XYZ')).toThrow('Unbekannte Währung')
  })
})

3. Mocking: vi.mock, vi.fn und vi.spyOn

Mocking ist der Teil, bei dem Jest-Neulinge am längsten hängen. Vitest löst das mit einer kompakten, gut dokumentierten API, die Claude Code ausgezeichnet beherrscht.

vi.mock — Module ersetzen

vi.mock ersetzt ein komplettes Modul durch eine Mock-Implementierung. Typischer Anwendungsfall: API-Calls in Tests unterbinden.

import { vi, describe, it, expect, beforeEach } from 'vitest'
import { getUser } from './userService'
import * as api from '../lib/api'

vi.mock('../lib/api')

describe('getUser', () => {
  beforeEach(() => {
    vi.mocked(api.fetchUser).mockResolvedValue({
      id: 1,
      name: 'Anna Muster',
      email: 'anna@example.com'
    })
  })

  it('gibt den User mit korrektem Namen zurück', async () => {
    const user = await getUser(1)
    expect(user.name).toBe('Anna Muster')
    expect(api.fetchUser).toHaveBeenCalledWith(1)
  })
})

vi.fn — Einzelne Funktionen mocken

vi.fn erzeugt eine Mock-Funktion, die Aufrufe aufzeichnet und beliebige Rückgabewerte liefern kann:

const onSubmit = vi.fn()
onSubmit.mockReturnValue(true)

onSubmit('test-data')

expect(onSubmit).toHaveBeenCalledOnce()
expect(onSubmit).toHaveBeenCalledWith('test-data')

vi.spyOn — Implementierungen überwachen

vi.spyOn ist nützlich, wenn du eine Methode überwachen willst, ohne sie vollständig zu ersetzen:

import { vi } from 'vitest'
import * as logger from './logger'

const spy = vi.spyOn(logger, 'warn')

// ... Code ausführen der warn aufruft

expect(spy).toHaveBeenCalledWith('Ungültiger Wert')

Claude Code schreibt Mocks zuverlässig und wählt automatisch die richtige Variante — vi.mock für Modul-Grenzen, vi.fn für Callbacks, vi.spyOn für partielle Überwachung. Einfach die zu testende Funktion zeigen und fragen:

claude "Schreib einen Vitest für diese Funktion, mock den API-Call
und prüfe dass die Fehlerbehandlung greift wenn der Server 500 zurückgibt"

4. Snapshot-Tests

Snapshot-Tests sichern die Ausgabe einer Komponente oder Funktion ab — beim ersten Lauf wird ein Snapshot gespeichert, bei jedem weiteren Lauf wird verglichen. Abweichungen schlagen fehl, beabsichtigte Änderungen werden mit vitest --update-snapshots akzeptiert.

import { describe, it, expect } from 'vitest'
import { render } from '@testing-library/react'
import { ProductCard } from './ProductCard'

describe('ProductCard', () => {
  it('rendert korrekt (Snapshot)', () => {
    const { container } = render(
      <ProductCard
        name="Kopfhörer Pro"
        price={149.99}
        inStock={true}
      />
    )
    expect(container).toMatchSnapshot()
  })
})

Snapshot-Disziplin: Snapshots sind nützlich für stabile UI-Komponenten, aber sie verfallen schnell zur Pflicht ohne Erkenntnisgewinn, wenn man sie blind akzeptiert. Lass Claude Code beim Review helfen: übergib den Snapshot-Diff und frag, ob die Änderung beabsichtigt oder ein Regressionszeichen ist.

5. Coverage mit @vitest/coverage-v8

Coverage-Berichte zeigen, welche Code-Pfade deine Tests abdecken. Mit dem bereits konfigurierten @vitest/coverage-v8 genügt:

npx vitest run --coverage

Das erzeugt einen Text-Report im Terminal und eine HTML-Ansicht unter coverage/index.html. Typische Ziele im Produktivbetrieb: 80 % Statement-Coverage für Kern-Logik, 60 % insgesamt. Coverage-Schwellen in der Konfiguration durchsetzen:

coverage: {
  thresholds: {
    statements: 80,
    branches: 70,
    functions: 75,
    lines: 80
  }
}

Claude Code analysiert deinen Coverage-Report und identifiziert, welche Pfade noch nicht abgedeckt sind — und schreibt direkt die fehlenden Tests:

claude "Hier ist mein Coverage-Report. Schreib Tests für die
uncovered branches in src/auth/session.ts" < coverage/coverage-summary.json

6. UI-Tests mit jsdom

Für React-, Vue- oder Svelte-Komponenten braucht Vitest eine DOM-Emulation. Mit environment: 'jsdom' in der Konfiguration und @testing-library/react funktionieren vollständige Render-Tests ohne Browser:

import { render, screen, fireEvent } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import { LoginForm } from './LoginForm'

it('blockiert Submit bei leerem Passwort', async () => {
  const onLogin = vi.fn()
  render(<LoginForm onLogin={onLogin} />)

  await userEvent.type(screen.getByLabelText('E-Mail'), 'test@example.com')
  await userEvent.click(screen.getByRole('button', { name: /anmelden/i }))

  expect(screen.getByText('Passwort ist erforderlich')).toBeInTheDocument()
  expect(onLogin).not.toHaveBeenCalled()
})

Claude Code schreibt diese Tests besonders effektiv, weil es die Komponente liest, ihre Props und ihren internen Zustand versteht und daraus sinnvolle Testszenarien ableitet — statt generischer Assertions, die zwar grün werden, aber nichts Relevantes prüfen.

7. Claude Code-Tipps für Vitest

Einige Prompts, die im Alltag besonders gut funktionieren:

"Das Schreiben von Tests ist die Arbeit, die am meisten profitiert, wenn man Claude Code Zugriff auf den vollständigen Code-Kontext gibt. Die Qualität der Assertions steigt dramatisch, sobald Claude Code die Implementierung kennt und nicht nur die Funktion erraten muss."

Zwei verwandte Artikel aus dieser Serie:


Claude Code Mastery — von Unit-Tests bis zum produktiven Agenten

Vitest ist ein Kapitel. Im Kurs lernst du Agents, MCP-Server, Hooks, Multi-Agent-Workflows und mehr. Vollständig auf Deutsch, einmalig bezahlt.

Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht

Kurs · Claude Code Mastery

Von Unit-Tests zum produktiven AI-Agenten

Testing. Agents. MCP. Hooks. Multi-Agent-Workflows. Alles auf Deutsch, einmalig bezahlt — kein Abo, keine Plattformabhängigkeit.

Jetzt einsteigen → Kursübersicht ansehen →

Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht