Back

Comment interroger le DOM dans React Testing Library

Comment interroger le DOM dans React Testing Library

Les requêtes DOM de React Testing Library constituent la base de tests de composants efficaces, pourtant de nombreux développeurs peinent à choisir la bonne méthode de requête pour leurs scénarios de test spécifiques. Que vous ayez affaire à des éléments synchrones, des composants asynchrones, ou un rendu conditionnel, comprendre quand utiliser les méthodes getBy, findBy et queryBy peut faire la différence entre des tests fiables et des tests instables.

Ce guide couvre les méthodes de requête essentielles de React Testing Library, leurs différences comportementales, et des exemples pratiques pour vous aider à écrire des tests plus robustes. Vous apprendrez les directives de priorité des requêtes, les pièges courants à éviter, et les modèles de test du monde réel qui améliorent la fiabilité des tests.

Points clés à retenir

  • Utilisez les requêtes getBy pour les éléments qui doivent être immédiatement présents après le rendu
  • Utilisez les requêtes findBy pour les éléments qui apparaissent de manière asynchrone, comme après des appels d’API ou des mises à jour d’état
  • Utilisez les requêtes queryBy lors du test de l’absence d’éléments dans le DOM
  • Privilégiez les requêtes getByRole et getByLabelText pour de meilleurs tests d’accessibilité
  • Réservez getByTestId aux scénarios complexes où les requêtes sémantiques ne sont pas pratiques
  • Déboguez les requêtes échouées en utilisant screen.debug() et Testing Playground pour une meilleure sélection de requêtes

Débuter avec les requêtes DOM de React Testing Library

React Testing Library s’appuie sur DOM Testing Library en ajoutant des APIs spécifiquement conçues pour tester les composants React. Installez-le dans votre projet React :

npm install --save-dev @testing-library/react @testing-library/jest-dom

Les requêtes DOM de React Testing Library fonctionnent en trouvant des éléments dans l’arbre DOM du composant rendu, similairement à la façon dont les utilisateurs interagissent avec votre application. Voici un exemple de base :

import { render, screen } from '@testing-library/react'
import LoginForm from './LoginForm'

test('renders login form', () => {
  render(<LoginForm />)
  const usernameInput = screen.getByLabelText('Username')
  expect(usernameInput).toBeInTheDocument()
})

La différence clé avec DOM Testing Library est que React Testing Library gère automatiquement les préoccupations spécifiques à React comme le rendu de composants, les mises à jour d’état, et le nettoyage.

Comprendre les types de requêtes de React Testing Library

React Testing Library fournit trois types principaux de requêtes, chacun avec des comportements différents pour gérer les éléments manquants et le timing.

Requêtes getBy - Sélection synchrone d’éléments

Les requêtes getBy retournent des éléments immédiatement et lèvent des erreurs si les éléments ne sont pas trouvés ou si plusieurs correspondances existent :

// Élément unique - lève une erreur si non trouvé ou si plusieurs trouvés
const button = screen.getByRole('button', { name: 'Submit' })

// Éléments multiples - lève une erreur si aucun trouvé
const listItems = screen.getAllByRole('listitem')

Utilisez les requêtes getBy quand vous vous attendez à ce que les éléments soient présents dans le DOM immédiatement après le rendu.

Requêtes findBy - Requêtes DOM asynchrones

Les requêtes findBy retournent des promesses et réessaient jusqu’à ce que les éléments apparaissent ou qu’un timeout se produise (par défaut 1000ms) :

// Attendre qu'un élément asynchrone apparaisse
const successMessage = await screen.findByText('Profile updated successfully')

// Éléments asynchrones multiples
const loadedItems = await screen.findAllByTestId('product-card')

Utilisez les requêtes findBy pour les éléments qui apparaissent après des appels d’API, des mises à jour d’état, ou d’autres opérations asynchrones.

Requêtes queryBy - Test conditionnel d’éléments

Les requêtes queryBy retournent null quand les éléments ne sont pas trouvés, ce qui les rend parfaites pour tester l’absence d’éléments :

// Tester qu'un élément n'existe pas
const errorMessage = screen.queryByText('Error occurred')
expect(errorMessage).not.toBeInTheDocument()

// Éléments multiples - retourne un tableau vide si aucun trouvé
const hiddenElements = screen.queryAllByTestId('hidden-item')
expect(hiddenElements).toHaveLength(0)
Type de requête0 correspondance1 correspondance>1 correspondancesAsynchrone
getByLève une erreurRetourne l’élémentLève une erreurNon
queryByRetourne nullRetourne l’élémentLève une erreurNon
findByLève une erreurRetourne l’élémentLève une erreurOui

Guide de priorité des requêtes React Testing Library

React Testing Library encourage le test des composants comme les utilisateurs interagissent avec eux. Ce guide de priorité vous aide à choisir des requêtes qui reflètent le comportement réel des utilisateurs.

Requêtes axées sur l’accessibilité (getByRole, getByLabelText)

getByRole devrait être votre premier choix pour la plupart des éléments :

// Boutons, liens, contrôles de formulaire
const submitButton = screen.getByRole('button', { name: 'Create Account' })
const navigationLink = screen.getByRole('link', { name: 'About Us' })

// Titres avec des niveaux spécifiques
const pageTitle = screen.getByRole('heading', { level: 1 })

getByLabelText fonctionne mieux pour les champs de formulaire :

const emailInput = screen.getByLabelText('Email Address')
const passwordInput = screen.getByLabelText(/password/i)

Ces requêtes garantissent que vos composants fonctionnent avec les technologies d’assistance.

Requêtes basées sur le contenu (getByText, getByPlaceholderText)

getByText trouve les éléments par leur contenu textuel visible :

// Correspondance de texte exacte
const welcomeMessage = screen.getByText('Welcome back, John!')

// Regex pour une correspondance flexible
const errorText = screen.getByText(/something went wrong/i)

getByPlaceholderText aide quand les labels ne sont pas disponibles :

const searchInput = screen.getByPlaceholderText('Search products...')

Quand utiliser getByTestId

Réservez getByTestId aux cas où les requêtes sémantiques ne sont pas suffisantes :

// Contenu dynamique où le texte change
const userAvatar = screen.getByTestId('user-avatar')

// Composants complexes sans rôles clairs
const chartContainer = screen.getByTestId('sales-chart')

Ajoutez les ID de test avec parcimonie et privilégiez les requêtes sémantiques chaque fois que possible.

Pièges courants des requêtes DOM de React Testing Library

Éviter les tests instables avec une sélection appropriée de requêtes

Problème : Utiliser des requêtes getBy pour des éléments qui se chargent de manière asynchrone :

// ❌ Instable - l'élément pourrait ne pas être encore chargé
test('shows user profile', () => {
  render(<UserProfile userId="123" />)
  const userName = screen.getByText('John Doe') // Peut échouer
})

Solution : Utilisez findBy pour les éléments asynchrones :

// ✅ Fiable - attend que l'élément apparaisse
test('shows user profile', async () => {
  render(<UserProfile userId="123" />)
  const userName = await screen.findByText('John Doe')
  expect(userName).toBeInTheDocument()
})

Déboguer les requêtes échouées

Quand les requêtes échouent, React Testing Library fournit des outils de débogage utiles :

// Voir ce qui est réellement dans le DOM
screen.debug()

// Obtenir des suggestions pour de meilleures requêtes
screen.getByRole('button') // Le message d'erreur suggère les rôles disponibles

Utilisez Testing Playground pour expérimenter avec les requêtes sur votre HTML réel.

Dépendance excessive aux ID de test

Problème : Utiliser getByTestId comme méthode de requête par défaut :

// ❌ Pas centré sur l'utilisateur
const button = screen.getByTestId('submit-button')

Solution : Utilisez des requêtes sémantiques qui reflètent l’interaction utilisateur :

// ✅ Centré sur l'utilisateur
const button = screen.getByRole('button', { name: 'Submit Form' })

Les ID de test devraient être votre dernier recours, pas votre premier choix.

Exemples concrets de React Testing Library

Voici des exemples pratiques montrant différentes méthodes de requête en action :

Test de formulaire :

import { render, screen, fireEvent } from '@testing-library/react'

test('handles form submission', async () => {
  render(<ContactForm />)
  
  // Utiliser getByLabelText pour les champs de formulaire
  const nameInput = screen.getByLabelText('Full Name')
  const emailInput = screen.getByLabelText('Email')
  const submitButton = screen.getByRole('button', { name: 'Send Message' })
  
  // Remplir le formulaire et soumettre
  fireEvent.change(nameInput, { target: { value: 'John Doe' } })
  fireEvent.change(emailInput, { target: { value: 'john@example.com' } })
  fireEvent.click(submitButton)
  
  // Attendre le message de succès
  const successMessage = await screen.findByText('Message sent successfully!')
  expect(successMessage).toBeInTheDocument()
})

Test d’état d’erreur :

import { rest } from 'msw'

test('displays error when API fails', async () => {
  // Simuler un échec d'API
  server.use(
    rest.get('/api/users', (req, res, ctx) => {
      return res(ctx.status(500))
    })
  )
  
  render(<UserList />)
  
  // Attendre que le message d'erreur apparaisse
  const errorMessage = await screen.findByText(/failed to load users/i)
  expect(errorMessage).toBeInTheDocument()
  
  // Vérifier que l'état de chargement a disparu
  const loadingSpinner = screen.queryByTestId('loading-spinner')
  expect(loadingSpinner).not.toBeInTheDocument()
})

Conclusion

Maîtriser les requêtes DOM de React Testing Library nécessite de comprendre quand utiliser getBy pour les éléments immédiats, findBy pour le contenu asynchrone, et queryBy pour tester l’absence d’éléments. Privilégiez les requêtes axées sur l’accessibilité comme getByRole et getByLabelText, et réservez getByTestId aux cas limites où les requêtes sémantiques ne sont pas suffisantes.

La clé de tests fiables est de choisir des requêtes qui reflètent la façon dont les utilisateurs interagissent avec vos composants. Cette approche crée des tests à la fois robustes et maintenables, capturant les vrais problèmes auxquels font face les utilisateurs tout en restant résistants aux changements d’implémentation.

FAQ

Utilisez les requêtes findBy lors du test d'éléments qui apparaissent après des opérations asynchrones comme des appels d'API, setTimeout, ou des mises à jour d'état. Les requêtes findBy réessaient automatiquement jusqu'à ce que l'élément apparaisse ou que le timeout soit atteint, évitant les tests instables.

Les requêtes getByTestId ne reflètent pas la façon dont les utilisateurs interagissent avec votre application. Les utilisateurs ne voient pas les ID de test - ils interagissent avec des boutons, lisent du texte, et utilisent des labels de formulaire. Les requêtes sémantiques comme getByRole et getByText créent des tests plus réalistes et maintenables.

Utilisez les requêtes queryBy, qui retournent null quand les éléments ne sont pas trouvés au lieu de lever des erreurs. Par exemple : expect(screen.queryByText('Error message')).not.toBeInTheDocument().

Les requêtes getAllBy retournent des tableaux d'éléments immédiatement et lèvent des erreurs si aucun élément n'est trouvé. Les requêtes findAllBy retournent des promesses qui se résolvent en tableaux et attendent qu'au moins un élément apparaisse avant de se résoudre.

Utilisez screen.debug() pour voir la structure DOM actuelle, vérifiez les messages d'erreur pour les suggestions de requêtes, et essayez Testing Playground pour expérimenter avec différentes approches de requête sur votre HTML réel.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers