Mestr React-komponenttest med React Testing Library. Lær bedste praksis for at skrive vedligeholdelsesvenlige, effektive tests, der fokuserer på brugeradfærd og tilgængelighed.
React Testing Library: Bedste praksis for komponenttestning for globale teams
I den stadigt udviklende verden af webudvikling er det afgørende at sikre pålideligheden og kvaliteten af dine React-applikationer. Dette gælder især for globale teams, der arbejder på projekter med forskellige brugerbaser og tilgængelighedskrav. React Testing Library (RTL) giver en kraftfuld og brugercentreret tilgang til komponenttestning. I modsætning til traditionelle testmetoder, der fokuserer på implementeringsdetaljer, opfordrer RTL dig til at teste dine komponenter, som en bruger ville interagere med dem, hvilket fører til mere robuste og vedligeholdelsesvenlige tests. Denne omfattende guide vil dykke ned i de bedste praksisser for at bruge RTL i dine React-projekter, med fokus på at bygge applikationer, der er egnede til et globalt publikum.
Hvorfor React Testing Library?
Før vi dykker ned i de bedste praksisser, er det afgørende at forstå, hvorfor RTL skiller sig ud fra andre testbiblioteker. Her er nogle af de vigtigste fordele:
- Brugercentreret tilgang: RTL prioriterer testning af komponenter fra brugerens perspektiv. Du interagerer med komponenten ved hjælp af de samme metoder, som en bruger ville (f.eks. at klikke på knapper, skrive i inputfelter), hvilket sikrer en mere realistisk og pålidelig testoplevelse.
- Fokus på tilgængelighed: RTL fremmer skrivning af tilgængelige komponenter ved at opfordre dig til at teste dem på en måde, der tager hensyn til brugere med handicap. Dette er i overensstemmelse med globale tilgængelighedsstandarder som WCAG.
- Reduceret vedligeholdelse: Ved at undgå at teste implementeringsdetaljer (f.eks. intern state, specifikke funktionskald) er RTL-tests mindre tilbøjelige til at gå i stykker, når du refaktorerer din kode. Dette fører til mere vedligeholdelsesvenlige og robuste tests.
- Forbedret kodedesign: Den brugercentrerede tilgang i RTL fører ofte til bedre komponentdesign, da du er tvunget til at tænke over, hvordan brugere vil interagere med dine komponenter.
- Fællesskab og økosystem: RTL har et stort og aktivt fællesskab, der giver rigelige ressourcer, support og udvidelser.
Opsætning af dit testmiljø
For at komme i gang med RTL skal du opsætte dit testmiljø. Her er en grundlæggende opsætning ved hjælp af Create React App (CRA), som leveres med Jest og RTL forudkonfigureret:
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`: Opretter et nyt React-projekt ved hjælp af Create React App.
- `cd my-react-app`: Navigerer ind i den nyoprettede projektmappe.
- `npm install --save-dev @testing-library/react @testing-library/jest-dom`: Installerer de nødvendige RTL-pakker som udviklingsafhængigheder. `@testing-library/react` leverer den centrale RTL-funktionalitet, mens `@testing-library/jest-dom` giver nyttige Jest-matchers til at arbejde med DOM.
Hvis du ikke bruger CRA, skal du installere Jest og RTL separat og konfigurere Jest til at bruge RTL.
Bedste praksis for komponenttestning med React Testing Library
1. Skriv tests, der ligner brugerinteraktioner
KernePrincippet i RTL er at teste komponenter, som en bruger ville gøre det. Dette betyder at fokusere på, hvad brugeren ser og gør, snarere end interne implementeringsdetaljer. Brug `screen`-objektet, som RTL stiller til rådighed, til at forespørge på elementer baseret på deres tekst, rolle eller tilgængelighedsetiketter.
Eksempel: Test af et knapklik
Lad os sige, at du har en simpel knapkomponent:
// Button.js
import React from 'react';
function Button({ onClick, children }) {
return ;
}
export default Button;
Sådan ville du teste den ved hjælp af RTL:
// Button.test.js
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
describe('Button Component', () => {
it('kalder onClick-handleren, når der klikkes', () => {
const handleClick = jest.fn();
render();
const buttonElement = screen.getByText('Klik mig');
fireEvent.click(buttonElement);
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
Forklaring:
- `render()`: Renderer Button-komponenten med en mock `onClick`-handler.
- `screen.getByText('Klik mig')`: Forespørger dokumentet for et element, der indeholder teksten "Klik mig". Det er sådan, en bruger ville identificere knappen.
- `fireEvent.click(buttonElement)`: Simulerer en klikhændelse på knapelementet.
- `expect(handleClick).toHaveBeenCalledTimes(1)`: Bekræfter, at `onClick`-handleren blev kaldt én gang.
Hvorfor dette er bedre end at teste implementeringsdetaljer: Forestil dig, at du refaktorerer Button-komponenten til at bruge en anden hændelseshandler eller ændrer den interne state. Hvis du testede den specifikke hændelseshandlerfunktion, ville din test gå i stykker. Ved at fokusere på brugerinteraktionen (at klikke på knappen) forbliver testen gyldig selv efter refaktorering.
2. Prioriter forespørgsler baseret på brugerintention
RTL giver forskellige forespørgselsmetoder til at finde elementer. Prioriter følgende forespørgsler i denne rækkefølge, da de bedst afspejler, hvordan brugere opfatter og interagerer med dine komponenter:
- getByRole: Denne forespørgsel er den mest tilgængelige og bør være dit første valg. Den giver dig mulighed for at finde elementer baseret på deres ARIA-roller (f.eks. button, link, heading).
- getByLabelText: Brug denne til at finde elementer, der er knyttet til en bestemt etiket, såsom inputfelter.
- getByPlaceholderText: Brug denne til at finde inputfelter baseret på deres pladsholdertekst.
- getByText: Brug denne til at finde elementer baseret på deres tekstindhold. Vær specifik og undgå at bruge generisk tekst, der kan forekomme flere steder.
- getByDisplayValue: Brug denne til at finde inputfelter baseret på deres aktuelle værdi.
Eksempel: Test af et formular-input
// Input.js
import React from 'react';
function Input({ label, placeholder, value, onChange }) {
return (
);
}
export default Input;
Sådan tester du det ved hjælp af den anbefalede forespørgselsrækkefølge:
// Input.test.js
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import Input from './Input';
describe('Input Component', () => {
it('opdaterer værdien, når brugeren skriver', () => {
const handleChange = jest.fn();
render();
const inputElement = screen.getByLabelText('Navn');
fireEvent.change(inputElement, { target: { value: 'John Doe' } });
expect(handleChange).toHaveBeenCalledTimes(1);
expect(handleChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: 'John Doe' } }));
});
});
Forklaring:
- `screen.getByLabelText('Navn')`: Bruger `getByLabelText` til at finde inputfeltet, der er forbundet med etiketten "Navn". Dette er den mest tilgængelige og brugervenlige måde at finde inputtet på.
3. Undgå at teste implementeringsdetaljer
Som nævnt tidligere, undgå at teste intern state, funktionskald eller specifikke CSS-klasser. Disse er implementeringsdetaljer, der kan ændres og kan føre til skrøbelige tests. Fokuser på komponentens observerbare adfærd.
Eksempel: Undgå at teste state direkte
I stedet for at teste, om en bestemt state-variabel er opdateret, skal du teste, om komponenten gengiver det korrekte output baseret på den state. For eksempel, hvis en komponent viser en meddelelse baseret på en boolesk state-variabel, skal du teste, om meddelelsen vises eller skjules, i stedet for at teste selve state-variablen.
4. Brug `data-testid` i specifikke tilfælde
Selvom det generelt er bedst at undgå at bruge `data-testid`-attributter, er der specifikke tilfælde, hvor de kan være nyttige:
- Elementer uden semantisk betydning: Hvis du har brug for at målrette et element, der ikke har en meningsfuld rolle, etiket eller tekst, kan du bruge `data-testid`.
- Komplekse komponentstrukturer: I komplekse komponentstrukturer kan `data-testid` hjælpe dig med at målrette specifikke elementer uden at være afhængig af skrøbelige selektorer.
- Tilgængelighedstestning: `data-testid` kan bruges til at identificere specifikke elementer under tilgængelighedstestning med værktøjer som Cypress eller Playwright.
Eksempel: Brug af `data-testid`
// MyComponent.js
import React from 'react';
function MyComponent() {
return (
Dette er min komponent.
);
}
export default MyComponent;
// MyComponent.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
it('renderer komponent-containeren', () => {
render( );
const containerElement = screen.getByTestId('my-component-container');
expect(containerElement).toBeInTheDocument();
});
});
Vigtigt: Brug `data-testid` sparsomt og kun når andre forespørgselsmetoder ikke er egnede.
5. Skriv meningsfulde testbeskrivelser
Klare og præcise testbeskrivelser er afgørende for at forstå formålet med hver test og for at fejlfinde fejl. Brug beskrivende navne, der tydeligt forklarer, hvad testen verificerer.
Eksempel: Gode vs. dårlige testbeskrivelser
Dårlig: `it('virker')`
God: `it('viser den korrekte hilsen')`
Endnu bedre: `it('viser hilsenen "Hello, World!", når name-prop ikke er angivet')`
Det bedre eksempel angiver tydeligt komponentens forventede adfærd under specifikke forhold.
6. Hold dine tests små og fokuserede
Hver test bør fokusere på at verificere et enkelt aspekt af komponentens adfærd. Undgå at skrive store, komplekse tests, der dækker flere scenarier. Små, fokuserede tests er lettere at forstå, vedligeholde og fejlfinde.
7. Brug test-doubles (mocks og spies) hensigtsmæssigt
Test-doubles er nyttige til at isolere den komponent, du tester, fra dens afhængigheder. Brug mocks og spies til at simulere eksterne tjenester, API-kald eller andre komponenter.
Eksempel: Mocking af et API-kald
// 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('henter og viser en liste over brugere', async () => {
render( );
// Vent på, at dataene indlæses
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`-funktionen til at returnere en foruddefineret liste af brugere. Dette giver dig mulighed for at teste komponenten uden at være afhængig af et rigtigt API-endpoint.
- `await waitFor(() => screen.getByText('John Doe'))`: Venter på, at teksten "John Doe" vises i dokumentet. Dette er nødvendigt, fordi dataene hentes asynkront.
8. Test kanttilfælde og fejlhåndtering
Test ikke kun den glade vej. Sørg for at teste kanttilfælde, fejlscenarier og grænsebetingelser. Dette vil hjælpe dig med at identificere potentielle problemer tidligt og sikre, at din komponent håndterer uventede situationer elegant.
Eksempel: Test af fejlhåndtering
Forestil dig en komponent, der henter data fra et API og viser en fejlmeddelelse, hvis API-kaldet mislykkes. Du bør skrive en test for at verificere, at fejlmeddelelsen vises korrekt, når API-kaldet mislykkes.
9. Fokuser på tilgængelighed
Tilgængelighed er afgørende for at skabe inkluderende webapplikationer. Brug RTL til at teste tilgængeligheden af dine komponenter og sikre, at de opfylder tilgængelighedsstandarder som WCAG. Nogle vigtige overvejelser om tilgængelighed inkluderer:
- Semantisk HTML: Brug semantiske HTML-elementer (f.eks. `
- ARIA-attributter: Brug ARIA-attributter til at give yderligere information om elementers rolle, tilstand og egenskaber, især for brugerdefinerede komponenter.
- Tastaturnavigation: Sørg for, at alle interaktive elementer er tilgængelige via tastaturnavigation.
- Farvekontrast: Brug tilstrækkelig farvekontrast for at sikre, at tekst er læselig for brugere med nedsat syn.
- Skærmlæserkompatibilitet: Test dine komponenter med en skærmlæser for at sikre, at de giver en meningsfuld og forståelig oplevelse for brugere med synshandicap.
Eksempel: Test af tilgængelighed 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('renderer en tilgængelig knap med den korrekte aria-label', () => {
render( );
const buttonElement = screen.getByRole('button', { name: 'Luk' });
expect(buttonElement).toBeInTheDocument();
});
});
Forklaring:
- `screen.getByRole('button', { name: 'Luk' })`: Bruger `getByRole` til at finde et knapelement med det tilgængelige navn "Luk". Dette sikrer, at knappen er korrekt mærket for skærmlæsere.
10. Integrer testning i din udviklingsworkflow
Testning bør være en integreret del af din udviklingsworkflow, ikke en eftertanke. Integrer dine tests i din CI/CD-pipeline for automatisk at køre tests, når kode committes eller deployes. Dette vil hjælpe dig med at fange fejl tidligt og forhindre regressioner.
11. Overvej lokalisering og internationalisering (i18n)
For globale applikationer er det afgørende at overveje lokalisering og internationalisering (i18n) under testning. Sørg for, at dine komponenter renderes korrekt på forskellige sprog og i forskellige locales.
Eksempel: Test af lokalisering
Hvis du bruger et bibliotek som `react-intl` eller `i18next` til lokalisering, kan du mocke lokaliseringskonteksten i dine tests for at verificere, at dine komponenter viser den korrekte oversatte tekst.
12. Brug brugerdefinerede render-funktioner til genbrugelig opsætning
Når du arbejder på større projekter, kan du opleve, at du gentager de samme opsætningstrin i flere tests. For at undgå duplikering, opret brugerdefinerede render-funktioner, der indkapsler den fælles opsætningslogik.
Eksempel: Brugerdefineret render-funktion
// 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-eksporter alt
export * from '@testing-library/react'
// overskriv render-metoden
export { customRender as render }
// MyComponent.test.js
import React from 'react';
import { render, screen } from './test-utils'; // Importer den brugerdefinerede render
import MyComponent from './MyComponent';
describe('MyComponent', () => {
it('renderer korrekt med temaet', () => {
render( );
// Din testlogik her
});
});
Dette eksempel opretter en brugerdefineret render-funktion, der indpakker komponenten med en ThemeProvider. Dette giver dig mulighed for nemt at teste komponenter, der er afhængige af temaet, uden at skulle gentage ThemeProvider-opsætningen i hver test.
Konklusion
React Testing Library tilbyder en kraftfuld og brugercentreret tilgang til komponenttestning. Ved at følge disse bedste praksisser kan du skrive vedligeholdelsesvenlige, effektive tests, der fokuserer på brugeradfærd og tilgængelighed. Dette vil føre til mere robuste, pålidelige og inkluderende React-applikationer for et globalt publikum. Husk at prioritere brugerinteraktioner, undgå at teste implementeringsdetaljer, fokusere på tilgængelighed og integrere testning i din udviklingsworkflow. Ved at omfavne disse principper kan du bygge højkvalitets React-applikationer, der imødekommer brugernes behov over hele verden.
Vigtigste pointer:
- Fokuser på brugerinteraktioner: Test komponenter, som en bruger ville interagere med dem.
- Prioriter tilgængelighed: Sørg for, at dine komponenter er tilgængelige for brugere med handicap.
- Undgå implementeringsdetaljer: Test ikke intern state eller funktionskald.
- Skriv klare og præcise tests: Gør dine tests lette at forstå og vedligeholde.
- Integrer testning i din workflow: Automatiser dine tests og kør dem regelmæssigt.
- Overvej globale målgrupper: Sørg for, at dine komponenter fungerer godt på forskellige sprog og i forskellige locales.