Mestre React-komponenttesting med React Testing Library. Lær beste praksis for å skrive vedlikeholdbare, effektive tester som fokuserer på brukeratferd og tilgjengelighet.
React Testing Library: Beste praksis for komponenttesting for globale team
I den stadig utviklende verdenen av webutvikling er det avgjørende å sikre påliteligheten og kvaliteten til dine React-applikasjoner. Dette gjelder spesielt for globale team som jobber med prosjekter med mangfoldige brukergrupper og krav til tilgjengelighet. React Testing Library (RTL) tilbyr en kraftig og brukersentrert tilnærming til komponenttesting. I motsetning til tradisjonelle testmetoder som fokuserer på implementeringsdetaljer, oppfordrer RTL deg til å teste komponentene dine slik en bruker ville interagert med dem, noe som fører til mer robuste og vedlikeholdbare tester. Denne omfattende guiden vil dykke ned i beste praksis for å bruke RTL i dine React-prosjekter, med fokus på å bygge applikasjoner som passer for et globalt publikum.
Hvorfor React Testing Library?
Før vi dykker ned i beste praksis, er det avgjørende å forstå hvorfor RTL skiller seg ut fra andre testbiblioteker. Her er noen sentrale fordeler:
- Brukersentrert tilnærming: RTL prioriterer testing av komponenter fra brukerens perspektiv. Du interagerer med komponenten ved å bruke de samme metodene som en bruker ville (f.eks. klikke på knapper, skrive i input-felt), noe som sikrer en mer realistisk og pålitelig testopplevelse.
- Fokus på tilgjengelighet: RTL fremmer skriving av tilgjengelige komponenter ved å oppmuntre deg til å teste dem på en måte som tar hensyn til brukere med nedsatt funksjonsevne. Dette er i tråd med globale tilgjengelighetsstandarder som WCAG.
- Redusert vedlikehold: Ved å unngå å teste implementeringsdetaljer (f.eks. intern state, spesifikke funksjonskall), er det mindre sannsynlig at RTL-tester brekker når du refaktorerer koden din. Dette fører til mer vedlikeholdbare og robuste tester.
- Forbedret kodedesign: Den brukersentrerte tilnærmingen til RTL fører ofte til bedre komponentdesign, ettersom du blir tvunget til å tenke på hvordan brukere vil interagere med komponentene dine.
- Fellesskap og økosystem: RTL har et stort og aktivt fellesskap, som gir rikelig med ressurser, støtte og utvidelser.
Sette opp testmiljøet ditt
For å komme i gang med RTL, må du sette opp testmiljøet ditt. Her er et grunnleggende oppsett ved hjelp av Create React App (CRA), som kommer med Jest og RTL forhåndskonfigurert:
npx create-react-app my-react-app
cd my-react-app
npm install --save-dev @testing-library/react @testing-library/jest-dom
Forklaring:
- `npx create-react-app my-react-app`: Oppretter et nytt React-prosjekt ved hjelp av Create React App.
- `cd my-react-app`: Navigerer inn i den nyopprettede prosjektmappen.
- `npm install --save-dev @testing-library/react @testing-library/jest-dom`: Installerer de nødvendige RTL-pakkene som utviklingsavhengigheter. `@testing-library/react` gir kjernefunksjonaliteten til RTL, mens `@testing-library/jest-dom` gir nyttige Jest-matchere for å jobbe med DOM.
Hvis du ikke bruker CRA, må du installere Jest og RTL separat og konfigurere Jest til å bruke RTL.
Beste praksis for komponenttesting med React Testing Library
1. Skriv tester som ligner brukerinteraksjoner
Kjerneprinsippet i RTL er å teste komponenter slik en bruker ville gjort. Dette betyr å fokusere på hva brukeren ser og gjør, i stedet for interne implementeringsdetaljer. Bruk `screen`-objektet levert av RTL for å søke etter elementer basert på deres tekst, rolle eller tilgjengelighetsetiketter.
Eksempel: Teste et knappeklikk
La oss si du har en enkel knappekomponent:
// Button.js
import React from 'react';
function Button({ onClick, children }) {
return ;
}
export default Button;
Slik ville du testet den med 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);
});
});
Forklaring:
- `render()`: Rendrer Button-komponenten med en mock `onClick`-handler.
- `screen.getByText('Click Me')`: Søker i dokumentet etter et element som inneholder teksten "Click Me". Slik ville en bruker identifisert knappen.
- `fireEvent.click(buttonElement)`: Simulerer en klikk-hendelse på knappen.
- `expect(handleClick).toHaveBeenCalledTimes(1)`: Bekrefter at `onClick`-handleren ble kalt én gang.
Hvorfor dette er bedre enn å teste implementeringsdetaljer: Tenk deg at du refaktorerer Button-komponenten til å bruke en annen hendelsesbehandler eller endrer den interne tilstanden. Hvis du testet den spesifikke hendelsesbehandlerfunksjonen, ville testen din blitt ødelagt. Ved å fokusere på brukerinteraksjonen (å klikke på knappen), forblir testen gyldig selv etter refaktorering.
2. Prioriter søk basert på brukerintensjon
RTL tilbyr forskjellige søkemetoder for å finne elementer. Prioriter følgende søk i denne rekkefølgen, da de best gjenspeiler hvordan brukere oppfatter og interagerer med komponentene dine:
- getByRole: Dette søket er det mest tilgjengelige og bør være ditt førstevalg. Det lar deg finne elementer basert på deres ARIA-roller (f.eks. knapp, lenke, overskrift).
- getByLabelText: Bruk dette for å finne elementer knyttet til en spesifikk etikett, for eksempel input-felt.
- getByPlaceholderText: Bruk dette for å finne input-felt basert på deres placeholder-tekst.
- getByText: Bruk dette for å finne elementer basert på deres tekstinnhold. Vær spesifikk og unngå å bruke generisk tekst som kan vises flere steder.
- getByDisplayValue: Bruk dette for å finne input-felt basert på deres nåværende verdi.
Eksempel: Teste et skjemainput
// Input.js
import React from 'react';
function Input({ label, placeholder, value, onChange }) {
return (
);
}
export default Input;
Slik tester du den ved å bruke den anbefalte søkerekkefølgen:
// 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' } }));
});
});
Forklaring:
- `screen.getByLabelText('Name')`: Bruker `getByLabelText` for å finne input-feltet knyttet til etiketten "Name". Dette er den mest tilgjengelige og brukervennlige måten å finne input-feltet på.
3. Unngå å teste implementeringsdetaljer
Som nevnt tidligere, unngå å teste intern state, funksjonskall eller spesifikke CSS-klasser. Dette er implementeringsdetaljer som kan endres og føre til skjøre tester. Fokuser på den observerbare atferden til komponenten.
Eksempel: Unngå å teste state direkte
I stedet for å teste om en spesifikk state-variabel er oppdatert, test om komponenten rendrer riktig output basert på den state-en. For eksempel, hvis en komponent viser en melding basert på en boolsk state-variabel, test om meldingen vises eller skjules, i stedet for å teste selve state-variabelen.
4. Bruk `data-testid` i spesifikke tilfeller
Selv om det generelt er best å unngå å bruke `data-testid`-attributter, finnes det spesifikke tilfeller der de kan være nyttige:
- Elementer uten semantisk betydning: Hvis du trenger å målrette mot et element som ikke har en meningsfull rolle, etikett eller tekst, kan du bruke `data-testid`.
- Komplekse komponentstrukturer: I komplekse komponentstrukturer kan `data-testid` hjelpe deg med å målrette mot spesifikke elementer uten å stole på skjøre selektorer.
- Tilgjengelighetstesting: `data-testid` kan brukes til å identifisere spesifikke elementer under tilgjengelighetstesting med verktøy som Cypress eller Playwright.
Eksempel: Bruke `data-testid`
// 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();
});
});
Viktig: Bruk `data-testid` med måte og bare når andre søkemetoder ikke er egnet.
5. Skriv meningsfulle testbeskrivelser
Tydelige og konsise testbeskrivelser er avgjørende for å forstå formålet med hver test og for feilsøking. Bruk beskrivende navn som tydelig forklarer hva testen verifiserer.
Eksempel: Gode vs. dårlige testbeskrivelser
Dårlig: `it('works')`
Bra: `it('viser riktig hilsen')`
Enda bedre: `it('viser hilsenen "Hei, Verden!" når name-propen ikke er angitt')`
Det bedre eksempelet angir tydelig den forventede atferden til komponenten under spesifikke forhold.
6. Hold testene dine små og fokuserte
Hver test bør fokusere på å verifisere ett enkelt aspekt av komponentens atferd. Unngå å skrive store, komplekse tester som dekker flere scenarier. Små, fokuserte tester er lettere å forstå, vedlikeholde og feilsøke.
7. Bruk test-doubles (mocks og spies) på riktig måte
Test-doubles er nyttige for å isolere komponenten du tester fra dens avhengigheter. Bruk mocks og spies for å simulere eksterne tjenester, API-kall eller andre komponenter.
Eksempel: Mocke et API-kall
// 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();
});
});
Forklaring:
- `global.fetch = jest.fn(...)`: Mocker `fetch`-funksjonen til å returnere en forhåndsdefinert liste med brukere. Dette lar deg teste komponenten uten å være avhengig av et ekte API-endepunkt.
- `await waitFor(() => screen.getByText('John Doe'))`: Venter på at teksten "John Doe" skal vises i dokumentet. Dette er nødvendig fordi dataene hentes asynkront.
8. Test kanttilfeller og feilhåndtering
Ikke bare test den ideelle flyten. Sørg for å teste kanttilfeller, feilscenarier og grensebetingelser. Dette vil hjelpe deg med å identifisere potensielle problemer tidlig og sikre at komponenten din håndterer uventede situasjoner på en elegant måte.
Eksempel: Teste feilhåndtering
Tenk deg en komponent som henter data fra et API og viser en feilmelding hvis API-kallet mislykkes. Du bør skrive en test for å verifisere at feilmeldingen vises korrekt når API-kallet mislykkes.
9. Fokuser på tilgjengelighet
Tilgjengelighet er avgjørende for å skape inkluderende webapplikasjoner. Bruk RTL for å teste tilgjengeligheten til komponentene dine og sikre at de oppfyller tilgjengelighetsstandarder som WCAG. Noen viktige hensyn til tilgjengelighet inkluderer:
- Semantisk HTML: Bruk semantiske HTML-elementer (f.eks. `
- ARIA-attributter: Bruk ARIA-attributter for å gi tilleggsinformasjon om rollen, tilstanden og egenskapene til elementer, spesielt for egendefinerte komponenter.
- Tastaturnavigasjon: Sørg for at alle interaktive elementer er tilgjengelige via tastaturnavigasjon.
- Fargekontrast: Bruk tilstrekkelig fargekontrast for å sikre at tekst er lesbar for brukere med nedsatt syn.
- Skjermleserkompatibilitet: Test komponentene dine med en skjermleser for å sikre at de gir en meningsfull og forståelig opplevelse for brukere med synshemninger.
Eksempel: Teste tilgjengelighet med `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();
});
});
Forklaring:
- `screen.getByRole('button', { name: 'Close' })`: Bruker `getByRole` for å finne et knappeelement med det tilgjengelige navnet "Close". Dette sikrer at knappen er riktig merket for skjermlesere.
10. Integrer testing i utviklingsflyten din
Testing bør være en integrert del av utviklingsflyten din, ikke en ettertanke. Integrer testene dine i CI/CD-pipelinen for å kjøre tester automatisk hver gang kode blir commitet eller deployert. Dette vil hjelpe deg med å fange feil tidlig og forhindre regresjoner.
11. Vurder lokalisering og internasjonalisering (i18n)
For globale applikasjoner er det avgjørende å vurdere lokalisering og internasjonalisering (i18n) under testing. Sørg for at komponentene dine rendrer korrekt på forskjellige språk og i ulike locale-innstillinger.
Eksempel: Teste lokalisering
Hvis du bruker et bibliotek som `react-intl` eller `i18next` for lokalisering, kan du mocke lokaliseringskonteksten i testene dine for å verifisere at komponentene dine viser riktig oversatt tekst.
12. Bruk egendefinerte render-funksjoner for gjenbrukbart oppsett
Når du jobber med større prosjekter, kan du oppleve at du gjentar de samme oppsettstrinnene i flere tester. For å unngå duplisering, lag egendefinerte render-funksjoner som innkapsler den felles oppsettslogikken.
Eksempel: Egendefinert render-funksjon
// 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'; // Importer den egendefinerte render-funksjonen
import MyComponent from './MyComponent';
describe('MyComponent', () => {
it('renders correctly with the theme', () => {
render( );
// Din testlogikk her
});
});
Dette eksempelet lager en egendefinert render-funksjon som pakker inn komponenten med en ThemeProvider. Dette lar deg enkelt teste komponenter som er avhengige av temaet uten å måtte gjenta ThemeProvider-oppsettet i hver test.
Konklusjon
React Testing Library tilbyr en kraftig og brukersentrert tilnærming til komponenttesting. Ved å følge disse beste praksisene kan du skrive vedlikeholdbare, effektive tester som fokuserer på brukeratferd og tilgjengelighet. Dette vil føre til mer robuste, pålitelige og inkluderende React-applikasjoner for et globalt publikum. Husk å prioritere brukerinteraksjoner, unngå å teste implementeringsdetaljer, fokusere på tilgjengelighet og integrere testing i utviklingsflyten din. Ved å omfavne disse prinsippene kan du bygge høykvalitets React-applikasjoner som møter behovene til brukere over hele verden.
Viktige punkter:
- Fokuser på brukerinteraksjoner: Test komponenter slik en bruker ville interagert med dem.
- Prioriter tilgjengelighet: Sørg for at komponentene dine er tilgjengelige for brukere med nedsatt funksjonsevne.
- Unngå implementeringsdetaljer: Ikke test intern state eller funksjonskall.
- Skriv tydelige og konsise tester: Gjør testene dine enkle å forstå og vedlikeholde.
- Integrer testing i arbeidsflyten din: Automatiser testene dine og kjør dem jevnlig.
- Tenk på globale publikum: Sørg for at komponentene dine fungerer bra på forskjellige språk og i ulike locale-innstillinger.