Passer au contenu

Tester React avec Vitest

Ce guide vous présente comment configurer et utiliser Vitest pour tester efficacement vos applications React en combinaison avec React Testing Library.

Configuration initiale

Pour commencer à tester vos composants React avec Vitest, vous devez installer les dépendances nécessaires :

Fenêtre de terminal
# Installation de Vitest et des dépendances de test React
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom

Ensuite, configurez Vitest dans votre fichier vitest.config.ts (ou vitest.config.js) :

vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: './src/test/setup.ts',
},
})

Créez un fichier de configuration pour les tests :

src/test/setup.ts
import '@testing-library/jest-dom'
// Configuration globale des tests

Structure des tests

Voici comment vous pouvez organiser vos tests React :

src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ └── Button.test.tsx
│ ├── Card/
│ │ ├── Card.tsx
│ │ └── Card.test.tsx

Tester un composant simple

Commençons par tester un composant Button simple :

src/components/Button/Button.tsx
import React from 'react'
interface ButtonProps {
onClick?: () => void
children: React.ReactNode
disabled?: boolean
}
export const Button = ({ onClick, children, disabled = false }: ButtonProps) => {
return (
<button
className={`button ${disabled ? 'button--disabled' : ''}`}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
)
}

Voici un test de base pour ce composant :

src/components/Button/Button.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from './Button'
describe('Button', () => {
it('renders correctly with children', () => {
render(<Button>Click me</Button>)
expect(screen.getByText('Click me')).toBeInTheDocument()
})
it('calls onClick when clicked', () => {
const handleClick = vi.fn()
render(<Button onClick={handleClick}>Click me</Button>)
fireEvent.click(screen.getByText('Click me'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
it('does not call onClick when disabled', () => {
const handleClick = vi.fn()
render(<Button onClick={handleClick} disabled>Click me</Button>)
fireEvent.click(screen.getByText('Click me'))
expect(handleClick).not.toHaveBeenCalled()
})
it('applies the disabled class when disabled', () => {
render(<Button disabled>Click me</Button>)
const button = screen.getByText('Click me')
expect(button).toHaveClass('button--disabled')
expect(button).toBeDisabled()
})
})

React Testing Library: concepts clés

React Testing Library est une approche de test qui privilégie les tests axés sur l’utilisateur plutôt que sur l’implémentation. Voici les concepts essentiels :

Requêtes (Queries)

Les requêtes vous permettent de trouver des éléments dans le DOM :

// Par texte (préféré)
screen.getByText('Click me')
// Par rôle
screen.getByRole('button')
// Par attribut test-id
screen.getByTestId('submit-button')
// Par label
screen.getByLabelText('Username')

Les variantes incluent :

  • getBy* : trouve un élément ou échoue
  • queryBy* : trouve un élément ou retourne null (utile pour vérifier l’absence)
  • findBy* : attend de manière asynchrone qu’un élément apparaisse

Événements utilisateur

Utilisez fireEvent ou userEvent pour simuler des interactions utilisateur :

// Clic simple
fireEvent.click(button)
// Saisie dans un champ
fireEvent.change(input, { target: { value: 'Hello' } })
// Avec userEvent (plus réaliste)
import userEvent from '@testing-library/user-event'
await userEvent.type(input, 'Hello')
await userEvent.click(button)

Tester les hooks avec renderHook

Pour tester des hooks personnalisés, utilisez renderHook :

src/hooks/useCounter.ts
import { useState } from 'react'
export function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue)
const increment = () => setCount(prev => prev + 1)
const decrement = () => setCount(prev => prev - 1)
return { count, increment, decrement }
}
// src/hooks/useCounter.test.ts
import { renderHook, act } from '@testing-library/react'
import { useCounter } from './useCounter'
describe('useCounter', () => {
it('initializes with default value', () => {
const { result } = renderHook(() => useCounter())
expect(result.current.count).toBe(0)
})
it('initializes with custom value', () => {
const { result } = renderHook(() => useCounter(10))
expect(result.current.count).toBe(10)
})
it('increments the counter', () => {
const { result } = renderHook(() => useCounter())
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})
it('decrements the counter', () => {
const { result } = renderHook(() => useCounter(5))
act(() => {
result.current.decrement()
})
expect(result.current.count).toBe(4)
})
})

Tester du code asynchrone

Utilisez waitFor et findBy* pour tester le code asynchrone :

import { render, screen, waitFor } from '@testing-library/react'
import { UserProfile } from './UserProfile'
// Composant qui charge des données utilisateur
it('displays user data when loaded', async () => {
render(<UserProfile userId="1" />)
// Vérifier le chargement
expect(screen.getByText('Loading...')).toBeInTheDocument()
// Attendre que les données soient chargées
await waitFor(() => {
expect(screen.queryByText('Loading...')).not.toBeInTheDocument()
})
// Ou utiliser findBy qui attend automatiquement
const username = await screen.findByText('John Doe')
expect(username).toBeInTheDocument()
})

Mocker des modules

Pour mocker des modules externes ou internes, utilisez les fonctionnalités de mock de Vitest :

import { vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import { UserList } from './UserList'
import * as api from '../api/users'
// Mock du module api
vi.mock('../api/users', () => ({
fetchUsers: vi.fn().mockResolvedValue([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
])
}))
it('renders users from API', async () => {
render(<UserList />)
// Vérifier que la fonction a été appelée
expect(api.fetchUsers).toHaveBeenCalledTimes(1)
// Attendre les résultats
const john = await screen.findByText('John')
const jane = await screen.findByText('Jane')
expect(john).toBeInTheDocument()
expect(jane).toBeInTheDocument()
})

Tests de snapshot

Les snapshots sont utiles pour s’assurer que le rendu des composants ne change pas de manière inattendue :

it('matches snapshot', () => {
const { container } = render(<Button>Click me</Button>)
expect(container).toMatchSnapshot()
})

Conclusion

Vitest combiné avec React Testing Library offre une façon puissante et intuitive de tester vos composants React. Cette approche vous encourage à écrire des tests qui reflètent la façon dont les utilisateurs interagissent réellement avec votre application, plutôt que de tester les détails d’implémentation.

Dans les prochains tutoriels, nous explorerons des techniques plus avancées pour tester les fonctionnalités complexes comme les formulaires, les contextes, le routage et les appels API.