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 :
# 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 Reactjsdom
: Un environnement qui simule le navigateur pour les tests@testing-library/react
et@testing-library/dom
: Pour tester les composants Reactvite-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 :
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
:
{
"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 :
npm install -D @testing-library/jest-dom
Crée un fichier test/vitest.setup.ts
:
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 :
// 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 :
// 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
:
// __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 :
// 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 :
// __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 :
- Mocké le client d'authentification pour simuler la fonction
signOut
- Testé que le bouton s'affiche correctement
- 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 :
// __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 :
- Mockons les fonctions
uploadFile
etonClientUploadComplete
- Rendons le composant
UploadDropzone
- Simulons un drag & drop de fichier
- 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
:
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é !