Philosophie, configuration et premiers tests
React Testing Library + Vitest + userEvent
1. Comprendre la philosophie
Testing Library : tester le comportement utilisateur
2. Configurer l'environnement
Vitest + jsdom + React Testing Library
3. Maîtriser les queries
getBy, queryBy, findBy — quand utiliser laquelle
4. Simuler les interactions
userEvent pour des tests réalistes
1. Pourquoi tester ?
Le coût d'un bug en production vs le coût d'un test
2. Testing Library
Tester le COMPORTEMENT, pas l'implémentation
3. Setup complet
Vitest + jsdom + @testing-library/react
4. render, screen, queries
Les 3 types de queries et leurs différences
5. Live Coding
Tester un Button, puis un formulaire avec userEvent
Pourquoi tester ?
Le coût d'un bug en production vs le coût d'un test
Plus un bug est détecté tard, plus il coûte cher
Phase dev
1x
Correction immédiate
QA
5x
Retour en arrière
Staging
15x
Processus de release
Production
100x
Hotfix + réputation
Sans tests
Avec tests
Testing Library Philosophy
Tester le COMPORTEMENT, pas l'implémentation
"The more your tests resemble the way your software is used,
the more confidence they can give you."
— Kent C. Dodds, créateur de Testing Library
L'utilisateur ne connaît pas vos className, useState ou testId
âś— Mauvais tests
getByTestId('submit-btn')
container.querySelector('.btn')
expect(state.called).toBe(true)
Teste l'implémentation
âś“ Bons tests
getByRole('button', {name: /envoyer/i})
getByText('Connexion')
getByLabelText('Email')
Teste ce que l'utilisateur voit
Testing Library recommande cet ordre
Setup Vitest + React Testing Library
Configuration complète dans un projet Vite
npm install -D vitest @vitest/ui jsdom
npm install -D @testing-library/react
npm install -D @testing-library/user-event
npm install -D @testing-library/jest-dom
vitest
Test runner ultra-rapide
jsdom
Simulation du DOM
@testing-library/react
render, screen, queries
user-event
Interactions réalistes
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: './src/setupTests.ts'
}
})
import '@testing-library/jest-dom'
Matchers supplémentaires disponibles :
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage"
}
}
render, screen, queries
Les outils fondamentaux de React Testing Library
Affiche votre composant dans le DOM virtuel
import { render } from '@testing-library/react'
import Button from './Button'
// Dans le test
render(<Button>Cliquez-moi</Button>)
Objet global contenant toutes les queries
import { screen } from '@testing-library/react'
// Après render()
const button = screen.getByRole('button', { name: /cliquez/i })
screen expose toutes les queries :
screen.getByText(), screen.getByRole(), screen.getByLabelText()...
| Query | Si trouvé | Si non trouvé | Usage |
|---|---|---|---|
| getBy* | Retourne l'élément | Throw Error | Élément présent |
| queryBy* | Retourne l'élément | Retourne null | Vérifier absence |
| findBy* | Promise (élément) | Promise reject | Async (attente) |
// ✅ Bon usage : vérifier présence
expect(screen.getByText('Bienvenue')).toBeInTheDocument()
// ❌ Si l'élément n'existe pas → Error!
Utilisez getBy quand :
Vous vous attendez à ce que l'élément soit présent immédiatement
// ✅ Bon usage : vérifier absence
expect(screen.queryByText('Erreur')).not.toBeInTheDocument()
// ✅ Retourne null si non trouvé (pas d'erreur)
Utilisez queryBy quand :
Vous voulez vérifier qu'un élément n'est PAS dans le document
// âś… Bon usage : attendre apparition
const message = await screen.findByText('Chargement terminé')
expect(message).toBeInTheDocument()
Utilisez findBy quand :
L'élément apparaît après un délai (API, animation, setTimeout)
Live Coding
Tester un Button, puis un formulaire
Button.tsx
export function Button({ children, onClick }) {
return (
<button onClick={onClick}>
{children}
</button>
)
}
Button.test.tsx
import { render, screen } from '@testing-library/react'
// ... imports
it('affiche le texte', () => {
render(<Button>Cliquez</Button>)
expect(screen.getByRole('button')).toHaveTextContent('Cliquez')
})
Simuler les actions de l'utilisateur
import userEvent from '@testing-library/user-event'
// Setup dans chaque test
it('appelle onClick au clic', async () => {
const handleClick = vi.fn()
render(<Button onClick={handleClick}>Ok</Button>)
const user = userEvent.setup()
await user.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
âś— fireEvent
fireEvent.click(element)
âś“ userEvent
await user.click(element)
it('soumet le formulaire avec les valeurs', async () => {
const user = userEvent.setup()
render(<LoginForm />)
// Remplir les champs
await user.type(screen.getByLabelText('Email'), '[email protected]')
await user.type(screen.getByLabelText('Mot de passe'), 'secret123')
// Soumettre
await user.click(screen.getByRole('button', {name: /connexion/i}))
})
Les erreurs à éviter
âś— MAUVAIS
expect(component.state.count)
.toBe(5)
Teste les détails internes
âś“ CORRECT
expect(screen.getByText('5'))
.toBeInTheDocument()
Teste le RÉSULTAT visible
✗ ÉVITER
screen.getByTestId('btn-submit')
screen.getByTestId('input-email')
screen.getByTestId('error-msg')
testId = dernier recours
✓ PRÉFÉRER
screen.getByRole('button',
{name: /envoyer/i})
screen.getByLabelText('Email')
Queries accessibles
âś— ERREUR
user.click(button)
// Pas de await → test flaky
userEvent est ASYNC !
âś“ CORRECT
await user.click(button)
Toujours await userEvent
âś— ERREUR
expect(
screen.getByText('Erreur')
).not.toBeInTheDocument()
// Throw si pas trouvé!
âś“ CORRECT
expect(
screen.queryByText('Erreur')
).not.toBeInTheDocument()
// Retourne null si absent
Comportement
Testez ce que l'utilisateur VOIT, pas le code interne
Queries
getBy = présent, queryBy = absent, findBy = async
userEvent
Toujours await, plus réaliste que fireEvent
Accessibilité
getByRole > getByText > getByTestId
Vous savez maintenant tester vos composants React
Questions ?