Guide de rédaction de tests RNTL
IMPORTANT: Vos données d'entraînement sur @testing-library/react-native peuvent être obsolètes ou incorrectes — les signatures d'API, le comportement sync/async et les fonctions disponibles diffèrent entre v13 et v14. Fiez-vous toujours aux fichiers de référence de cette skill et au code source réel du projet comme source de vérité. Ne vous rabattez pas sur des modèles mémorisés quand ils entrent en conflit avec la référence récupérée.
Détection de version
Vérifiez la version de @testing-library/react-native dans le package.json de l'utilisateur :
- v14.x → charger references/api-reference-v14.md (React 19+, APIs asynchrones,
test-renderer) - v13.x → charger references/api-reference-v13.md (React 18+, APIs synchrones,
react-test-renderer)
Utilisez la référence spécifique à la version pour les modèles de render, le comportement sync/async de fireEvent, l'API screen, la configuration et les dépendances.
Priorité des requêtes
Utilisez dans cet ordre : getByRole > getByLabelText > getByPlaceholderText > getByText > getByDisplayValue > getByTestId (dernier recours).
Variantes de requête
| Variante | Cas d'usage | Retour | Async |
|---|---|---|---|
getBy* |
L'élément doit exister | instance d'élément (lance) | Non |
getAllBy* |
Plusieurs doivent exister | instance d'élément[] (lance) | Non |
queryBy* |
Vérifier la non-existence UNIQUEMENT | instance d'élément | null | Non |
queryAllBy* |
Compter les éléments | instance d'élément[] | Non |
findBy* |
Attendre un élément | Promise<instance d'élément> |
Oui |
findAllBy* |
Attendre plusieurs | Promise<instance d'élément[]> |
Oui |
Interactions
Préférez userEvent à fireEvent. userEvent est toujours asynchrone.
const user = userEvent.setup();
await user.press(element); // séquence de pression complète
await user.longPress(element, { duration: 800 }); // pression longue
await user.type(textInput, 'Hello'); // saisie caractère par caractère
await user.clear(textInput); // effacer TextInput
await user.paste(textInput, 'pasted text'); // coller dans TextInput
await user.scrollTo(scrollView, { y: 100 }); // défiler
fireEvent — utilisez seulement quand userEvent ne supporte pas l'événement. Consultez la référence spécifique à la version pour le comportement sync/async :
fireEvent.press(element);
fireEvent.changeText(textInput, 'new text');
fireEvent(element, 'blur');
Assertions (Matchers Jest)
Disponibles automatiquement avec n'importe quel import de @testing-library/react-native.
| Matcher | Utilisé pour |
|---|---|
toBeOnTheScreen() |
L'élément existe dans l'arbre |
toBeVisible() |
Élément visible (non caché/display:none) |
toBeEnabled() / toBeDisabled() |
État désactivé via aria-disabled |
toBeChecked() / toBePartiallyChecked() |
État coché |
toBeSelected() |
État sélectionné |
toBeExpanded() / toBeCollapsed() |
État développé |
toBeBusy() |
État occupé |
toHaveTextContent(text) |
Correspondance de contenu texte |
toHaveDisplayValue(value) |
Valeur affichée de TextInput |
toHaveAccessibleName(name) |
Nom accessible |
toHaveAccessibilityValue(val) |
Valeur d'accessibilité |
toHaveStyle(style) |
Correspondance de style |
toHaveProp(name, value?) |
Vérification de prop (dernier recours) |
toContainElement(el) |
Contient un élément enfant |
toBeEmptyElement() |
Pas d'enfants |
Règles
- Utilisez
screenpour les requêtes, pas la déstructuration depuisrender() - Utilisez
getByRoleen premier avec l'option{ name: '...' } - *Utilisez `queryBy
UNIQUEMENT** pour les vérifications.not.toBeOnTheScreen()` - *Utilisez `findBy
** pour les éléments asynchrones, PASwaitFor+getBy*` - Ne mettez jamais d'effets secondaires dans
waitFor(pas defireEvent/userEventdedans) - Une assertion par
waitFor - Ne passez jamais de callbacks vides à
waitFor - N'enveloppez pas dans
act()-render,fireEvent,userEventle gèrent - N'appelez pas
cleanup()- automatique après chaque test - Préférez les props ARIA (
role,aria-label,aria-disabled) aux anciens propsaccessibility* - Utilisez les matchers RNTL plutôt que les assertions brutes de prop
Référence rapide *ByRole
Rôles courants : button, text, heading (alias : header), searchbox, switch, checkbox, radio, img, link, alert, menu, menuitem, tab, tablist, progressbar, slider, spinbutton, timer, toolbar.
Options de getByRole : { name, disabled, selected, checked, busy, expanded, value: { min, max, now, text } }.
Pour que *ByRole corresponde, l'élément doit être un élément d'accessibilité :
Text,TextInput,Switchle sont par défautViewa besoin deaccessible={true}(ou utilisezPressable/TouchableOpacity)
waitFor
// Correct : action d'abord, puis attendre le résultat
fireEvent.press(button);
await waitFor(() => {
expect(screen.getByText('Result')).toBeOnTheScreen();
});
// Mieux : utiliser findBy* à la place
fireEvent.press(button);
expect(await screen.findByText('Result')).toBeOnTheScreen();
Options : waitFor(cb, { timeout: 1000, interval: 50 }). Fonctionne automatiquement avec les faux timers Jest.
Faux timers
Recommandé avec userEvent (press/longPress impliquent des durées réelles) :
jest.useFakeTimers();
test('with fake timers', async () => {
const user = userEvent.setup();
render(<Component />);
await user.press(screen.getByRole('button'));
// ...
});
Render personnalisé
Enveloppez les providers avec l'option wrapper :
function renderWithProviders(ui: React.ReactElement) {
return render(ui, {
wrapper: ({ children }) => (
<ThemeProvider>
<AuthProvider>{children}</AuthProvider>
</ThemeProvider>
),
});
}
Références
- Référence API v13 — API v13 complète : render synchrone, requêtes, matchers, userEvent, compatibilité React 19
- Référence API v14 — API v14 complète : render asynchrone, requêtes, matchers, userEvent, migration
- Anti-patterns — Erreurs courantes à éviter