codelynx.dev
🇫🇷🇬🇧

Retour 28/04/2025

Ajouter des tests Vitest dans une application Next.js

Écris par Melvyn Malherbe le 28/04/2025


Comment ajouter des tests Vitest dans une application Next.js ? Vitest va te permettre de venir créer des tests unitaires pour s'assurer que tes composants React ou même tes méthodes sont correctes.

Installation

Première étape dans ton projet c'est d'installer les dépendances nécessaires :

BASH
# Avec npm
npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom vite-tsconfig-paths

# Avec pnpm
pnpm add -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom vite-tsconfig-paths

Chaque package a un rôle spécifique :

  • vitest : Le framework de test principal
  • @vitejs/plugin-react : Pour le support de React
  • jsdom : Un environnement qui simule le navigateur pour les tests
  • @testing-library/react et @testing-library/dom : Pour tester les composants React
  • vite-tsconfig-paths : Pour supporter les alias de path de ton tsconfig

Configuration

Après avoir installé ces packages, crée un fichier de configuration vitest.config.mts (ou .js si tu n'utilises pas TypeScript) à la racine de ton projet :

TYPESCRIPT
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'

export default defineConfig({
  plugins: [tsconfigPaths(), react()],
  test: {
    environment: 'jsdom',
  },
})

Cette configuration indique à Vitest d'utiliser l'environnement JSDOM pour simuler un navigateur et active les plugins React et les chemins de ton tsconfig.

Ajouter les scripts de test

Maintenant, ajoutons un script pour exécuter nos tests dans le fichier package.json :

JSON
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "test": "vitest",
    "test:ci": "vitest run"
  }
}

Le script test lancera Vitest en mode watch (il guettera les changements dans tes fichiers), tandis que test:ci exécutera les tests une seule fois, idéal pour l'intégration continue.

Configuration avancée (optionnelle)

Pour obtenir des assertions DOM plus riches comme toBeInTheDocument(), tu peux installer le package @testing-library/jest-dom et créer un fichier de configuration supplémentaire :

BASH
npm install -D @testing-library/jest-dom

Crée un fichier test/vitest.setup.ts :

TYPESCRIPT
import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterEach } from 'vitest'

// Nettoie automatiquement après chaque test
afterEach(() => {
  cleanup()
})

Ensuite, mets à jour la configuration Vitest pour utiliser ce fichier :

TYPESCRIPT
// vitest.config.mts
export default defineConfig({
  plugins: [tsconfigPaths(), react()],
  test: {
    environment: 'jsdom',
    setupFiles: './test/vitest.setup.ts',
  },
})

Créer ton premier test

Créons un test simple pour vérifier qu'un composant Page affiche correctement un titre. Imaginons que tu as un composant page dans ton app Next.js :

TSX
// app/page.tsx
import Link from 'next/link'

export default function Page() {
  return (
    <div>
      <h1>Accueil</h1>
      <Link href="/about">À propos</Link>
    </div>
  )
}

Créons un dossier __tests__ à la racine du projet et ajoutons un fichier page.test.tsx :

TSX
// __tests__/page.test.tsx
import { expect, test } from 'vitest'
import { render, screen } from '@testing-library/react'
import Page from '../app/page'

test('Page', () => {
  render(<Page />)
  expect(screen.getByRole('heading', { level: 1, name: 'Accueil' })).toBeDefined()
})

Ce test va rendre le composant Page et vérifier qu'il contient un titre de niveau 1 avec le texte "Accueil".

Tester un composant avec un état

Voyons maintenant comment tester un composant plus complexe, comme un bouton de déconnexion qui interagit avec un service d'authentification :

TSX
// components/logout.tsx
import { authClient } from "@/lib/auth-client"

export const LogoutButton = () => {
  return (
    <button onClick={() => authClient.signOut()}>
      Logout
    </button>
  )
}

Pour tester ce composant, nous allons devoir mocker le client d'authentification :

TSX
// __tests__/logout.test.tsx
import { LogoutButton } from "@/components/logout"
import { authClient } from "@/lib/auth-client"
import { fireEvent, render, screen } from "@testing-library/react"
import { beforeAll, describe, expect, it, vi } from "vitest"

beforeAll(() => {
  vi.mock("@/lib/auth-client", () => ({
    authClient: {
      signOut: vi.fn(),
    },
  }))
})

describe("LogoutButton", () => {
  it("should renders logout button correctly", () => {
    render(<LogoutButton />)
    expect(screen.getByRole("button", { name: "Logout" })).toBeDefined()
  })

  it("should call signOut when clicked", () => {
    render(<LogoutButton />)
    const button = screen.getByRole("button", { name: "Logout" })
    fireEvent.click(button)
    expect(authClient.signOut).toHaveBeenCalled()
  })
})

Ici, nous avons :

  1. Mocké le client d'authentification pour simuler la fonction signOut
  2. Testé que le bouton s'affiche correctement
  3. Testé que la fonction signOut est appelée quand on clique sur le bouton

Tester un composant avec des requêtes asynchrones

Pour les composants qui font des requêtes API ou ont un comportement asynchrone, nous pouvons utiliser waitFor ou act pour gérer les mises à jour asynchrones.

Prenons l'exemple d'un composant de zone de dépôt de fichiers :

TSX
// __tests__/upload-dropzone.test.tsx
import { UploadDropzone } from "@/components/upload-dropzone"
import { fireEvent, render, screen, waitFor } from "@testing-library/react"
import { describe, expect, it, vi } from "vitest"

describe("UploadDropzone", () => {
  it("should handle file drop correctly", async () => {
    const mockUploadFile = vi.fn()
    const mockOnClientUploadComplete = vi.fn()

    render(
      <UploadDropzone 
        uploadFile={mockUploadFile} 
        onClientUploadComplete={mockOnClientUploadComplete} 
      />
    )

    const dropzone = screen
      .getByText("Drag and drop or click to upload a file")
      .closest("div")
    const file = new File(["file contents"], "test.pdf", {
      type: "application/pdf",
    })

    if (dropzone) {
      // Simuler des événements de drag
      fireEvent.dragOver(dropzone)
      expect(screen.getByText("Drop the file here")).toBeDefined()

      // Créer un objet DataTransfer mock
      const dataTransfer = {
        files: [file],
      }

      fireEvent.drop(dropzone, { dataTransfer })

      await waitFor(() => {
        expect(mockUploadFile).toHaveBeenCalledWith(file)
        expect(mockOnClientUploadComplete).toHaveBeenCalled()
      })
    }
  })
})

Dans ce test, nous :

  1. Mockons les fonctions uploadFile et onClientUploadComplete
  2. Rendons le composant UploadDropzone
  3. Simulons un drag & drop de fichier
  4. Vérifions que les fonctions de mock sont appelées avec les bonnes valeurs

Intégration dans une CI/CD

Pour automatiser l'exécution des tests dans un pipeline CI/CD, tu peux ajouter un workflow comme celui-ci dans ton fichier .github/workflows/code-quality.yml :

YAML
vitest:
  runs-on: ubuntu-latest
  strategy:
    matrix:
      node-version: [22.x]
  steps:
    - uses: pnpm/action-setup@v2
      with:
        version: 8
    - uses: actions/checkout@v3
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
    - name: Install
      run: pnpm install
      env:
        CI: true
    - name: Run TypeScript check
      run: pnpm vitest:ci
      env:
        CI: true

Ce workflow exécutera tes tests Vitest dans l'environnement CI à chaque push ou pull request.

Conclusion

Vitest est un excellent choix pour tester tes applications Next.js. Avec sa syntaxe familière et sa rapidité d'exécution, il rend le processus de test beaucoup plus agréable. En combinaison avec React Testing Library, tu peux écrire des tests robustes qui vérifient le comportement de tes composants du point de vue de l'utilisateur.

Voici ce que nous avons appris :

  • Comment configurer Vitest dans une application Next.js
  • Comment écrire des tests simples pour des composants basiques
  • Comment mocker des dépendances et tester des interactions
  • Comment gérer les tests asynchrones
  • Comment intégrer les tests dans un workflow CI/CD

Maintenant que tu as toutes les bases, tu peux commencer à tester ta propre application et garantir sa qualité et sa stabilité !