Beheers het testen van React-componenten met React Testing Library. Leer best practices voor het schrijven van onderhoudbare, effectieve tests die focussen op gebruikersgedrag en toegankelijkheid.
React Testing Library: Best Practices voor Component Testing in Internationale Teams
In de constant evoluerende wereld van webontwikkeling is het waarborgen van de betrouwbaarheid en kwaliteit van je React-applicaties van het grootste belang. Dit geldt met name voor internationale teams die werken aan projecten met diverse gebruikersgroepen en toegankelijkheidseisen. React Testing Library (RTL) biedt een krachtige en gebruikersgerichte benadering voor het testen van componenten. In tegenstelling tot traditionele testmethoden die zich richten op implementatiedetails, moedigt RTL je aan om je componenten te testen zoals een gebruiker ermee zou interageren, wat leidt tot robuustere en beter onderhoudbare tests. Deze uitgebreide gids duikt in de best practices voor het gebruik van RTL in je React-projecten, met een focus op het bouwen van applicaties die geschikt zijn voor een wereldwijd publiek.
Waarom React Testing Library?
Voordat we ingaan op de best practices, is het cruciaal om te begrijpen waarom RTL zich onderscheidt van andere testbibliotheken. Hier zijn enkele belangrijke voordelen:
- Gebruikersgerichte Benadering: RTL geeft prioriteit aan het testen van componenten vanuit het perspectief van de gebruiker. Je interageert met het component via dezelfde methoden als een gebruiker (bv. op knoppen klikken, typen in invoervelden), wat zorgt voor een realistischere en betrouwbaardere testervaring.
- Focus op Toegankelijkheid: RTL promoot het schrijven van toegankelijke componenten door je aan te moedigen ze te testen op een manier die rekening houdt met gebruikers met een beperking. Dit sluit aan bij wereldwijde toegankelijkheidsstandaarden zoals WCAG.
- Minder Onderhoud: Door het testen van implementatiedetails (bv. interne state, specifieke functieaanroepen) te vermijden, is de kans kleiner dat RTL-tests breken wanneer je je code refactort. Dit leidt tot beter onderhoudbare en veerkrachtigere tests.
- Beter Codeontwerp: De gebruikersgerichte aanpak van RTL leidt vaak tot een beter componentontwerp, omdat je gedwongen wordt na te denken over hoe gebruikers met je componenten zullen interageren.
- Community en Ecosysteem: RTL heeft een grote en actieve community, die zorgt voor een overvloed aan bronnen, ondersteuning en extensies.
Je Testomgeving Opzetten
Om met RTL aan de slag te gaan, moet je je testomgeving opzetten. Hier is een basisconfiguratie met Create React App (CRA), dat standaard wordt geleverd met Jest en RTL:
npx create-react-app my-react-app
cd my-react-app
npm install --save-dev @testing-library/react @testing-library/jest-dom
Uitleg:
- `npx create-react-app my-react-app`: Maakt een nieuw React-project aan met Create React App.
- `cd my-react-app`: Navigeert naar de map van het zojuist aangemaakte project.
- `npm install --save-dev @testing-library/react @testing-library/jest-dom`: Installeert de benodigde RTL-pakketten als development dependencies. `@testing-library/react` levert de kernfunctionaliteit van RTL, terwijl `@testing-library/jest-dom` handige Jest-matchers biedt voor het werken met de DOM.
Als je geen CRA gebruikt, moet je Jest en RTL afzonderlijk installeren en Jest configureren om RTL te gebruiken.
Best Practices voor Component Testing met React Testing Library
1. Schrijf Tests die Lijken op Gebruikersinteracties
Het kernprincipe van RTL is om componenten te testen zoals een gebruiker dat zou doen. Dit betekent dat je je moet richten op wat de gebruiker ziet en doet, in plaats van op interne implementatiedetails. Gebruik het `screen`-object dat door RTL wordt geleverd om elementen te zoeken op basis van hun tekst, rol of toegankelijkheidslabels.
Voorbeeld: Een Klik op een Knop Testen
Stel, je hebt een eenvoudig knopcomponent:
// Button.js
import React from 'react';
function Button({ onClick, children }) {
return ;
}
export default Button;
Zo zou je het testen met RTL:
// Button.test.js
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
describe('Button Component', () => {
it('calls the onClick handler when clicked', () => {
const handleClick = jest.fn();
render();
const buttonElement = screen.getByText('Click Me');
fireEvent.click(buttonElement);
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
Uitleg:
- `render()`: Rendert het Button-component met een mock `onClick`-handler.
- `screen.getByText('Click Me')`: Zoekt in het document naar een element dat de tekst "Click Me" bevat. Dit is hoe een gebruiker de knop zou identificeren.
- `fireEvent.click(buttonElement)`: Simuleert een klik-event op het knopelement.
- `expect(handleClick).toHaveBeenCalledTimes(1)`: Verifieert dat de `onClick`-handler ƩƩn keer is aangeroepen.
Waarom dit beter is dan het testen van implementatiedetails: Stel je voor dat je het Button-component refactort om een andere event handler te gebruiken of de interne state te veranderen. Als je de specifieke event handler-functie zou testen, zou je test breken. Door te focussen op de gebruikersinteractie (het klikken op de knop), blijft de test geldig, zelfs na refactoring.
2. Geef Prioriteit aan Queries op Basis van Gebruikersintentie
RTL biedt verschillende query-methoden om elementen te vinden. Geef prioriteit aan de volgende queries in deze volgorde, omdat ze het best weerspiegelen hoe gebruikers je componenten waarnemen en ermee interageren:
- getByRole: Deze query is het meest toegankelijk en zou je eerste keus moeten zijn. Hiermee kun je elementen vinden op basis van hun ARIA-rollen (bv. button, link, heading).
- getByLabelText: Gebruik dit om elementen te vinden die geassocieerd zijn met een specifiek label, zoals invoervelden.
- getByPlaceholderText: Gebruik dit om invoervelden te vinden op basis van hun placeholder-tekst.
- getByText: Gebruik dit om elementen te vinden op basis van hun tekstinhoud. Wees specifiek en vermijd generieke tekst die op meerdere plaatsen kan voorkomen.
- getByDisplayValue: Gebruik dit om invoervelden te vinden op basis van hun huidige waarde.
Voorbeeld: Een Formulierinvoer Testen
// Input.js
import React from 'react';
function Input({ label, placeholder, value, onChange }) {
return (
);
}
export default Input;
Zo test je dit met de aanbevolen query-volgorde:
// Input.test.js
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import Input from './Input';
describe('Input Component', () => {
it('updates the value when the user types', () => {
const handleChange = jest.fn();
render();
const inputElement = screen.getByLabelText('Name');
fireEvent.change(inputElement, { target: { value: 'John Doe' } });
expect(handleChange).toHaveBeenCalledTimes(1);
expect(handleChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: 'John Doe' } }));
});
});
Uitleg:
- `screen.getByLabelText('Name')`: Gebruikt `getByLabelText` om het invoerveld te vinden dat is geassocieerd met het label "Name". Dit is de meest toegankelijke en gebruiksvriendelijke manier om de invoer te lokaliseren.
3. Vermijd het Testen van Implementatiedetails
Zoals eerder vermeld, vermijd het testen van interne state, functieaanroepen of specifieke CSS-klassen. Dit zijn implementatiedetails die aan verandering onderhevig zijn en kunnen leiden tot breekbare tests. Richt je op het waarneembare gedrag van het component.
Voorbeeld: Vermijd het Direct Testen van State
In plaats van te testen of een specifieke state-variabele wordt bijgewerkt, test je of het component de juiste output rendert op basis van die state. Als een component bijvoorbeeld een bericht toont op basis van een booleaanse state-variabele, test dan of het bericht wordt getoond of verborgen, in plaats van de state-variabele zelf te testen.
4. Gebruik `data-testid` voor Specifieke Gevallen
Hoewel het over het algemeen het beste is om het gebruik van `data-testid`-attributen te vermijden, zijn er specifieke gevallen waarin ze nuttig kunnen zijn:
- Elementen zonder Semantische Betekenis: Als je een element moet targeten dat geen betekenisvolle rol, label of tekst heeft, kun je `data-testid` gebruiken.
- Complexe Componentstructuren: In complexe componentstructuren kan `data-testid` je helpen specifieke elementen te targeten zonder afhankelijk te zijn van fragiele selectors.
- Toegankelijkheidstesten: `data-testid` kan worden gebruikt voor het identificeren van specifieke elementen tijdens toegankelijkheidstesten met tools zoals Cypress of Playwright.
Voorbeeld: `data-testid` gebruiken
// MyComponent.js
import React from 'react';
function MyComponent() {
return (
This is my component.
);
}
export default MyComponent;
// MyComponent.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
it('renders the component container', () => {
render( );
const containerElement = screen.getByTestId('my-component-container');
expect(containerElement).toBeInTheDocument();
});
});
Belangrijk: Gebruik `data-testid` spaarzaam en alleen wanneer andere query-methoden niet geschikt zijn.
5. Schrijf Betekenisvolle Testbeschrijvingen
Duidelijke en beknopte testbeschrijvingen zijn cruciaal om het doel van elke test te begrijpen en om fouten te debuggen. Gebruik beschrijvende namen die duidelijk uitleggen wat de test verifieert.
Voorbeeld: Goede vs. Slechte Testbeschrijvingen
Slecht: `it('works')`
Goed: `it('displays the correct greeting message')`
Nog Beter: `it('displays the greeting message "Hello, World!" when the name prop is not provided')`
Het betere voorbeeld geeft duidelijk het verwachte gedrag van het component onder specifieke omstandigheden weer.
6. Houd je Tests Klein en Gefocust
Elke test moet zich richten op het verifiƫren van een enkel aspect van het gedrag van het component. Vermijd het schrijven van grote, complexe tests die meerdere scenario's dekken. Kleine, gefocuste tests zijn gemakkelijker te begrijpen, te onderhouden en te debuggen.
7. Gebruik Test Doubles (Mocks en Spies) op de Juiste Manier
Test doubles zijn nuttig om het component dat je test te isoleren van zijn afhankelijkheden. Gebruik mocks en spies om externe services, API-aanroepen of andere componenten te simuleren.
Voorbeeld: Een API-aanroep Mocken
// UserList.js
import React, { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
async function fetchUsers() {
const response = await fetch('/api/users');
const data = await response.json();
setUsers(data);
}
fetchUsers();
}, []);
return (
{users.map(user => (
- {user.name}
))}
);
}
export default UserList;
// UserList.test.js
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import UserList from './UserList';
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' },
]),
})
);
describe('UserList Component', () => {
it('fetches and displays a list of users', async () => {
render( );
// Wait for the data to load
await waitFor(() => screen.getByText('John Doe'));
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
});
});
Uitleg:
- `global.fetch = jest.fn(...)`: Mockt de `fetch`-functie om een vooraf gedefinieerde lijst van gebruikers te retourneren. Dit stelt je in staat het component te testen zonder afhankelijk te zijn van een echt API-eindpunt.
- `await waitFor(() => screen.getByText('John Doe'))`: Wacht tot de tekst "John Doe" in het document verschijnt. Dit is nodig omdat de gegevens asynchroon worden opgehaald.
8. Test Edge Cases en Foutafhandeling
Test niet alleen het 'happy path'. Zorg ervoor dat je edge cases, foutscenario's en randvoorwaarden test. Dit helpt je om potentiƫle problemen vroegtijdig te identificeren en ervoor te zorgen dat je component op een elegante manier omgaat met onverwachte situaties.
Voorbeeld: Foutafhandeling Testen
Stel je een component voor dat gegevens ophaalt van een API en een foutmelding toont als de API-aanroep mislukt. Je zou een test moeten schrijven om te verifiƫren dat de foutmelding correct wordt weergegeven wanneer de API-aanroep faalt.
9. Focus op Toegankelijkheid
Toegankelijkheid is cruciaal voor het creƫren van inclusieve webapplicaties. Gebruik RTL om de toegankelijkheid van je componenten te testen en ervoor te zorgen dat ze voldoen aan toegankelijkheidsnormen zoals WCAG. Enkele belangrijke overwegingen voor toegankelijkheid zijn:
- Semantische HTML: Gebruik semantische HTML-elementen (bv. `
- ARIA-attributen: Gebruik ARIA-attributen om extra informatie te geven over de rol, status en eigenschappen van elementen, vooral voor aangepaste componenten.
- Toetsenbordnavigatie: Zorg ervoor dat alle interactieve elementen toegankelijk zijn via toetsenbordnavigatie.
- Kleurcontrast: Gebruik voldoende kleurcontrast om ervoor te zorgen dat tekst leesbaar is voor gebruikers met een visuele beperking.
- Compatibiliteit met Schermlezers: Test je componenten met een schermlezer om ervoor te zorgen dat ze een betekenisvolle en begrijpelijke ervaring bieden voor gebruikers met een visuele beperking.
Voorbeeld: Toegankelijkheid Testen met `getByRole`
// MyAccessibleComponent.js
import React from 'react';
function MyAccessibleComponent() {
return (
);
}
export default MyAccessibleComponent;
// MyAccessibleComponent.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import MyAccessibleComponent from './MyAccessibleComponent';
describe('MyAccessibleComponent', () => {
it('renders an accessible button with the correct aria-label', () => {
render( );
const buttonElement = screen.getByRole('button', { name: 'Close' });
expect(buttonElement).toBeInTheDocument();
});
});
Uitleg:
- `screen.getByRole('button', { name: 'Close' })`: Gebruikt `getByRole` om een knopelement te vinden met de toegankelijke naam "Close". Dit zorgt ervoor dat de knop correct is gelabeld voor schermlezers.
10. Integreer Testen in je Ontwikkelworkflow
Testen moet een integraal onderdeel zijn van je ontwikkelworkflow, niet iets wat je achteraf doet. Integreer je tests in je CI/CD-pipeline om tests automatisch uit te voeren telkens wanneer code wordt gecommit of gedeployed. Dit helpt je om bugs vroegtijdig op te sporen en regressies te voorkomen.
11. Houd Rekening met Lokalisatie en Internationalisatie (i18n)
Voor wereldwijde applicaties is het cruciaal om tijdens het testen rekening te houden met lokalisatie en internationalisatie (i18n). Zorg ervoor dat je componenten correct renderen in verschillende talen en locales.
Voorbeeld: Lokalisatie Testen
Als je een bibliotheek zoals `react-intl` of `i18next` gebruikt voor lokalisatie, kun je de lokalisatiecontext in je tests mocken om te verifiƫren dat je componenten de juiste vertaalde tekst weergeven.
12. Gebruik Aangepaste Render-functies voor Herbruikbare Setup
Bij het werken aan grotere projecten zul je merken dat je dezelfde setup-stappen in meerdere tests herhaalt. Om duplicatie te voorkomen, kun je aangepaste render-functies maken die de gemeenschappelijke setup-logica inkapselen.
Voorbeeld: Aangepaste Render-functie
// test-utils.js
import React from 'react';
import { render } from '@testing-library/react';
import { ThemeProvider } from 'styled-components';
import theme from './theme';
const AllTheProviders = ({ children }) => {
return (
{children}
);
}
const customRender = (ui, options) =>
render(ui, { wrapper: AllTheProviders, ...options })
// re-export everything
export * from '@testing-library/react'
// override render method
export { customRender as render }
// MyComponent.test.js
import React from 'react';
import { render, screen } from './test-utils'; // Import the custom render
import MyComponent from './MyComponent';
describe('MyComponent', () => {
it('renders correctly with the theme', () => {
render( );
// Your test logic here
});
});
Dit voorbeeld creƫert een aangepaste render-functie die het component omhult met een ThemeProvider. Hiermee kun je eenvoudig componenten testen die afhankelijk zijn van het thema, zonder de ThemeProvider-setup in elke test te hoeven herhalen.
Conclusie
React Testing Library biedt een krachtige en gebruikersgerichte benadering voor het testen van componenten. Door deze best practices te volgen, kun je onderhoudbare, effectieve tests schrijven die zich richten op gebruikersgedrag en toegankelijkheid. Dit leidt tot robuustere, betrouwbaardere en inclusievere React-applicaties voor een wereldwijd publiek. Onthoud dat je prioriteit moet geven aan gebruikersinteracties, het testen van implementatiedetails moet vermijden, je moet richten op toegankelijkheid en testen moet integreren in je ontwikkelworkflow. Door deze principes te omarmen, kun je hoogwaardige React-applicaties bouwen die voldoen aan de behoeften van gebruikers over de hele wereld.
Belangrijkste Punten:
- Focus op Gebruikersinteracties: Test componenten zoals een gebruiker ermee zou interageren.
- Geef Prioriteit aan Toegankelijkheid: Zorg ervoor dat je componenten toegankelijk zijn voor gebruikers met een beperking.
- Vermijd Implementatiedetails: Test geen interne state of functieaanroepen.
- Schrijf Duidelijke en Beknopte Tests: Maak je tests gemakkelijk te begrijpen en te onderhouden.
- Integreer Testen in je Workflow: Automatiseer je tests en voer ze regelmatig uit.
- Houd Rekening met een Wereldwijd Publiek: Zorg ervoor dat je componenten goed werken in verschillende talen en locales.