
Simulation d'événements utilisateur (`user-event`)
Apprenez à simuler de manière réaliste les interactions utilisateur (clics, saisie, etc.) dans vos tests React avec la bibliothèque @testing-library/user-event, un complément essentiel à RTL.
Pourquoi simuler les interactions utilisateur ?
Tester un composant React ne se limite pas à vérifier son rendu initial. La plupart des composants sont interactifs : les utilisateurs cliquent sur des boutons, saisissent du texte dans des champs, sélectionnent des options, survolent des éléments, etc. Pour s'assurer qu'un composant réagit correctement à ces actions, il est indispensable de pouvoir les simuler dans nos tests.
React Testing Library fournit une API de base appelée `fireEvent` pour déclencher des événements DOM. Cependant, une bibliothèque complémentaire, `@testing-library/user-event`, est aujourd'hui fortement recommandée car elle simule les interactions utilisateur de manière beaucoup plus fidèle au comportement réel d'un navigateur et d'un utilisateur humain.
`@testing-library/user-event` : Une simulation plus réaliste
La philosophie de `@testing-library/user-event` (souvent importé comme `userEvent`) s'aligne parfaitement avec celle de RTL : se concentrer sur ce que l'utilisateur fait réellement, plutôt que sur les événements DOM spécifiques qui sont déclenchés en coulisses. Un simple clic, par exemple, n'est pas juste un événement `click`. Dans un navigateur, il implique souvent une séquence d'événements : `mouseover`, `mousemove`, `mousedown`, `mouseup`, `click`, ainsi que la gestion du focus.
`userEvent` tente de reproduire cette complexité :
- Dispatch d'événements multiples : Une action `userEvent.click(button)` déclenchera la séquence d'événements appropriée qu'un navigateur déclencherait.
- Gestion de l'état (ex: focus, sélection) : Il maintient un état interne pour simuler le focus, la sélection de texte, l'état des touches modificatrices (Shift, Ctrl), etc.
- Gestion des éléments désactivés : Par défaut, `userEvent` ne permettra pas d'interagir avec des éléments désactivés (`disabled`), tout comme un utilisateur ne le pourrait pas. `fireEvent` le permettrait, ce qui peut masquer des bugs.
- Comportement asynchrone : La plupart des actions `userEvent` sont asynchrones et retournent des Promesses. Cela reflète mieux la nature asynchrone de nombreuses interactions dans le navigateur et nécessite l'utilisation de `async/await` dans vos tests.
- Support amélioré pour la saisie clavier (`type`) : `userEvent.type()` simule la saisie caractère par caractère, déclenchant les événements `keydown`, `keypress`, `keyup` appropriés pour chaque caractère, ce qui est beaucoup plus réaliste que `fireEvent.change()`.
Pour ces raisons, l'utilisation de `userEvent` conduit à des tests plus robustes, plus fiables et moins susceptibles de donner des faux positifs par rapport à `fireEvent`.
Installation et utilisation de base
Si vous utilisez une version récente de CRA ou de Vite avec React, `user-event` est probablement déjà inclus. Sinon, installez-le comme dépendance de développement :
npm install --save-dev @testing-library/user-event
# ou
yarn add --dev @testing-library/user-eventEnsuite, importez-le dans votre fichier de test :
import userEvent from '@testing-library/user-event';
// Note: Il est courant d'utiliser l'import par défautL'utilisation typique implique de trouver d'abord l'élément cible avec `screen` (ou les requêtes retournées par `render`), puis d'appeler la méthode `userEvent` appropriée, en utilisant `await` car la plupart des méthodes sont asynchrones.
Exemples d'interactions courantes
- Clic :
test('button click updates message', async () => { render(); const button = screen.getByRole('button', { name: /click me/i }); await userEvent.click(button); expect(screen.getByText(/message updated/i)).toBeInTheDocument(); }); - Saisie de texte :
test('typing in input updates its value', async () => { render(); const input = screen.getByLabelText(/username/i); await userEvent.type(input, 'Hello React'); expect(input).toHaveValue('Hello React'); }); - Effacer un champ :
test('clearing input works', async () => { render(); const input = screen.getByLabelText(/username/i); expect(input).toHaveValue('some text'); await userEvent.clear(input); expect(input).toHaveValue(''); }); - Sélectionner une option dans un `select` :
test('selecting an option changes value', async () => { render(); const select = screen.getByRole('combobox'); const optionToSelect = screen.getByRole('option', { name: 'Option 2' }); await userEvent.selectOptions(select, optionToSelect); // Ou par valeur : // await userEvent.selectOptions(select, 'value2'); expect(select).toHaveValue('value2'); expect(optionToSelect.selected).toBe(true); }); - Survol (Hover) : Utile pour tester les tooltips ou les menus qui apparaissent au survol.
test('hovering over element shows tooltip', async () => { render(); const elementToHover = screen.getByText(/hover over me/i); // Le tooltip n'est pas visible initialement expect(screen.queryByRole('tooltip')).not.toBeInTheDocument(); await userEvent.hover(elementToHover); expect(await screen.findByRole('tooltip')).toBeVisible(); // Utiliser findBy* car l'apparition peut être asynchrone await userEvent.unhover(elementToHover); expect(screen.queryByRole('tooltip')).not.toBeInTheDocument(); }); - Interactions clavier complexes : `userEvent.keyboard()` permet de simuler des séquences de touches, y compris les touches spéciales (Shift, Enter, Tab, etc.).
test('typing and pressing Enter submits form', async () => { render(); const input = screen.getByLabelText(/search/i); await userEvent.keyboard('React Testing{Enter}'); // Vérifier que la soumission a eu lieu... });
`userEvent` vs `fireEvent`
Comme mentionné, `userEvent` est généralement préférable à `fireEvent`. `fireEvent` est une API plus bas niveau qui déclenche uniquement l'événement DOM spécifié (par exemple, `fireEvent.click(button)` ne déclenche que l'événement `click`). Cela peut être utile dans de rares cas où vous devez tester le déclenchement d'un événement très spécifique ou un comportement qui ne correspond pas à une interaction utilisateur typique.
Cependant, pour la grande majorité des tests d'interaction, `userEvent` offre une simulation plus fidèle, réduisant le risque de tests qui passent alors que l'application réelle échouerait (faux positifs) et augmentant la confiance globale dans vos tests. La règle générale est : utilisez `userEvent` par défaut, et ne recourez à `fireEvent` que si vous avez une raison très spécifique de vouloir déclencher un événement DOM isolé.
Conclusion : Des interactions plus fiables
L'intégration de `@testing-library/user-event` dans votre flux de travail de test React est une étape clé pour améliorer la qualité et la fiabilité de vos tests. En simulant les interactions de manière plus proche de la réalité, vous créez des tests qui non seulement vérifient la logique de vos composants mais aussi leur utilisabilité effective. N'oubliez pas d'utiliser `async/await` avec la plupart des méthodes `userEvent` pour gérer correctement leur nature asynchrone.