Apgūstiet React Testing Library (RTL) ar šo pilnīgo ceļvedi. Uzziniet, kā rakstīt efektīvus, uzturamus un uz lietotāju orientētus testus savām React lietotnēm, koncentrējoties uz labāko praksi un reāliem piemēriem.
React Testing Library: Visaptverošs Ceļvedis
Mūsdienu straujajā tīmekļa izstrādes vidē jūsu React lietotņu kvalitātes un uzticamības nodrošināšana ir vissvarīgākā. React Testing Library (RTL) ir kļuvusi par populāru un efektīvu risinājumu, lai rakstītu testus, kas vērsti uz lietotāja perspektīvu. Šis ceļvedis sniedz pilnīgu pārskatu par RTL, aptverot visu, sākot no pamatkoncepcijām līdz pat progresīvām tehnikām, dodot jums iespēju veidot robustas un uzturamas React lietotnes.
Kāpēc izvēlēties React Testing Library?
Tradicionālās testēšanas pieejas bieži balstās uz implementācijas detaļām, padarot testus trauslus un pakļautus lūšanai pie nelielām koda izmaiņām. Savukārt RTL mudina jūs testēt savus komponentus tā, kā ar tiem mijiedarbotos lietotājs, koncentrējoties uz to, ko lietotājs redz un piedzīvo. Šī pieeja piedāvā vairākas galvenās priekšrocības:
- Uz lietotāju vērsta testēšana: RTL veicina tādu testu rakstīšanu, kas atspoguļo lietotāja perspektīvu, nodrošinot, ka jūsu lietotne darbojas, kā paredzēts no gala lietotāja viedokļa.
- Samazināts testu trauslums: Izvairoties no implementācijas detaļu testēšanas, RTL testi retāk lūzīs, kad jūs pārveidosiet savu kodu, kas noved pie uzturamākiem un robustākiem testiem.
- Uzlabots koda dizains: RTL mudina rakstīt komponentus, kas ir pieejami un viegli lietojami, kas noved pie labāka kopējā koda dizaina.
- Fokuss uz pieejamību: RTL atvieglo jūsu komponentu pieejamības testēšanu, nodrošinot, ka jūsu lietotne ir lietojama ikvienam.
- Vienkāršots testēšanas process: RTL nodrošina vienkāršu un intuitīvu API, kas atvieglo testu rakstīšanu un uzturēšanu.
Testēšanas vides iestatīšana
Pirms varat sākt lietot RTL, jums ir jāiestata sava testēšanas vide. Tas parasti ietver nepieciešamo atkarību instalēšanu un testēšanas ietvara konfigurēšanu.
Priekšnosacījumi
- Node.js un npm (vai yarn): Pārliecinieties, ka jūsu sistēmā ir instalēts Node.js un npm (vai yarn). Jūs varat tos lejupielādēt no oficiālās Node.js vietnes.
- React projekts: Jums vajadzētu būt esošam React projektam vai izveidot jaunu, izmantojot Create React App vai līdzīgu rīku.
Instalēšana
Instalējiet šādas pakotnes, izmantojot npm vai yarn:
npm install --save-dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Vai, izmantojot yarn:
yarn add --dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Paku skaidrojums:
- @testing-library/react: Galvenā bibliotēka React komponentu testēšanai.
- @testing-library/jest-dom: Nodrošina pielāgotus Jest saskaņotājus (matchers) apgalvojumiem par DOM mezgliem.
- Jest: Populārs JavaScript testēšanas ietvars.
- babel-jest: Jest pārveidotājs, kas izmanto Babel, lai kompilētu jūsu kodu.
- @babel/preset-env: Babel priekšiestatījums, kas nosaka nepieciešamos Babel spraudņus un priekšiestatījumus, lai atbalstītu jūsu mērķa vides.
- @babel/preset-react: Babel priekšiestatījums priekš React.
Konfigurācija
Izveidojiet `babel.config.js` failu sava projekta saknes direktorijā ar šādu saturu:
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};
Atjauniniet savu `package.json` failu, lai iekļautu testēšanas skriptu:
{
"scripts": {
"test": "jest"
}
}
Izveidojiet `jest.config.js` failu sava projekta saknes direktorijā, lai konfigurētu Jest. Minimāla konfigurācija varētu izskatīties šādi:
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['/src/setupTests.js'],
};
Izveidojiet `src/setupTests.js` failu ar šādu saturu. Tas nodrošina, ka Jest DOM saskaņotāji ir pieejami visos jūsu testos:
import '@testing-library/jest-dom/extend-expect';
Pirmā testa rakstīšana
Sāksim ar vienkāršu piemēru. Pieņemsim, ka jums ir React komponents, kas parāda sveiciena ziņojumu:
// src/components/Greeting.js
import React from 'react';
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
export default Greeting;
Tagad uzrakstīsim testu šim komponentam:
// src/components/Greeting.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import Greeting from './Greeting';
test('renders a greeting message', () => {
render(<Greeting name="World" />);
const greetingElement = screen.getByText(/Hello, World!/i);
expect(greetingElement).toBeInTheDocument();
});
Skaidrojums:
- `render`: Šī funkcija renderē komponentu DOM.
- `screen`: Šis objekts nodrošina metodes DOM vaicājumu veikšanai.
- `getByText`: Šī metode atrod elementu pēc tā teksta satura. `/i` karodziņš padara meklēšanu reģistrnejutīgu.
- `expect`: Šī funkcija tiek izmantota, lai veiktu apgalvojumus par komponenta uzvedību.
- `toBeInTheDocument`: Šis saskaņotājs apgalvo, ka elements atrodas DOM.
Lai palaistu testu, izpildiet šādu komandu savā terminālī:
npm test
Ja viss ir pareizi konfigurēts, testam vajadzētu būt sekmīgam.
Biežākās RTL vaicājumu metodes
RTL nodrošina dažādas vaicājumu metodes elementu atrašanai DOM. Šīs metodes ir izstrādātas, lai atdarinātu, kā lietotāji mijiedarbojas ar jūsu lietotni.
`getByRole`
Šis vaicājums atrod elementu pēc tā ARIA lomas. Ir laba prakse izmantot `getByRole`, kad vien iespējams, jo tas veicina pieejamību un nodrošina, ka jūsu testi ir noturīgi pret izmaiņām pamatā esošajā DOM struktūrā.
<button role="button">Click me</button>
const buttonElement = screen.getByRole('button');
expect(buttonElement).toBeInTheDocument();
`getByLabelText`
Šis vaicājums atrod elementu pēc tā saistītās etiķetes teksta. Tas ir noderīgs formu elementu testēšanai.
<label htmlFor="name">Name:</label>
<input type="text" id="name" />
const nameInputElement = screen.getByLabelText('Name:');
expect(nameInputElement).toBeInTheDocument();
`getByPlaceholderText`
Šis vaicājums atrod elementu pēc tā viettura teksta.
<input type="text" placeholder="Enter your email" />
const emailInputElement = screen.getByPlaceholderText('Enter your email');
expect(emailInputElement).toBeInTheDocument();
`getByAltText`
Šis vaicājums atrod attēla elementu pēc tā alternatīvā teksta (alt text). Ir svarīgi nodrošināt jēgpilnu alternatīvo tekstu visiem attēliem, lai nodrošinātu pieejamību.
<img src="logo.png" alt="Company Logo" />
const logoImageElement = screen.getByAltText('Company Logo');
expect(logoImageElement).toBeInTheDocument();
`getByTitle`
Šis vaicājums atrod elementu pēc tā `title` atribūta.
<span title="Close">X</span>
const closeElement = screen.getByTitle('Close');
expect(closeElement).toBeInTheDocument();
`getByDisplayValue`
Šis vaicājums atrod elementu pēc tā parādītās vērtības. Tas ir noderīgs formu ievades lauku testēšanai ar iepriekš aizpildītām vērtībām.
<input type="text" value="Initial Value" />
const inputElement = screen.getByDisplayValue('Initial Value');
expect(inputElement).toBeInTheDocument();
`getAllBy*` vaicājumi
Papildus `getBy*` vaicājumiem, RTL nodrošina arī `getAllBy*` vaicājumus, kas atgriež atbilstošo elementu masīvu. Tie ir noderīgi, ja jums jāapgalvo, ka DOM ir vairāki elementi ar vienādām īpašībām.
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
const listItems = screen.getAllByRole('listitem');
expect(listItems).toHaveLength(3);
`queryBy*` vaicājumi
`queryBy*` vaicājumi ir līdzīgi `getBy*` vaicājumiem, bet tie atgriež `null`, ja netiek atrasts atbilstošs elements, nevis met kļūdu. Tas ir noderīgi, ja vēlaties apgalvot, ka elements *nav* klāt DOM.
const missingElement = screen.queryByText('Non-existent text');
expect(missingElement).toBeNull();
`findBy*` vaicājumi
`findBy*` vaicājumi ir `getBy*` vaicājumu asinhronās versijas. Tie atgriež solījumu (Promise), kas tiek atrisināts, kad tiek atrasts atbilstošais elements. Tie ir noderīgi asinhronu darbību testēšanai, piemēram, datu ielādei no API.
// Simulating an asynchronous data fetch
const fetchData = () => new Promise(resolve => {
setTimeout(() => resolve('Data Loaded!'), 1000);
});
function MyComponent() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
fetchData().then(setData);
}, []);
return <div>{data}</div>;
}
test('loads data asynchronously', async () => {
render(<MyComponent />);
const dataElement = await screen.findByText('Data Loaded!');
expect(dataElement).toBeInTheDocument();
});
Lietotāja mijiedarbību simulēšana
RTL nodrošina `fireEvent` un `userEvent` API, lai simulētu lietotāja mijiedarbības, piemēram, pogu klikšķināšanu, teksta ievadi ievades laukos un formu iesniegšanu.
`fireEvent`
`fireEvent` ļauj programmatiski izraisīt DOM notikumus. Tā ir zemāka līmeņa API, kas sniedz jums precīzu kontroli pār izsauktajiem notikumiem.
<button onClick={() => alert('Button clicked!')}>Click me</button>
import { fireEvent } from '@testing-library/react';
test('simulates a button click', () => {
const alertMock = jest.spyOn(window, 'alert').mockImplementation(() => {});
render(<button onClick={() => alert('Button clicked!')}>Click me</button>);
const buttonElement = screen.getByRole('button');
fireEvent.click(buttonElement);
expect(alertMock).toHaveBeenCalledTimes(1);
alertMock.mockRestore();
});
`userEvent`
`userEvent` ir augstāka līmeņa API, kas reālistiskāk simulē lietotāja mijiedarbības. Tā apstrādā tādas detaļas kā fokusa pārvaldība un notikumu secība, padarot jūsu testus robustākus un mazāk trauslus.
<input type="text" onChange={e => console.log(e.target.value)} />
import userEvent from '@testing-library/user-event';
test('simulates typing in an input field', () => {
const inputElement = screen.getByRole('textbox');
userEvent.type(inputElement, 'Hello, world!');
expect(inputElement).toHaveValue('Hello, world!');
});
Asinhronā koda testēšana
Daudzas React lietotnes ietver asinhronas darbības, piemēram, datu ielādi no API. RTL nodrošina vairākus rīkus asinhronā koda testēšanai.
`waitFor`
`waitFor` ļauj jums gaidīt, līdz nosacījums kļūst patiess, pirms veikt apgalvojumu. Tas ir noderīgs, testējot asinhronas darbības, kuru pabeigšanai nepieciešams zināms laiks.
function MyComponent() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
setTimeout(() => {
setData('Data loaded!');
}, 1000);
}, []);
return <div>{data}</div>;
}
import { waitFor } from '@testing-library/react';
test('waits for data to load', async () => {
render(<MyComponent />);
await waitFor(() => screen.getByText('Data loaded!'));
const dataElement = screen.getByText('Data loaded!');
expect(dataElement).toBeInTheDocument();
});
`findBy*` vaicājumi
Kā minēts iepriekš, `findBy*` vaicājumi ir asinhroni un atgriež solījumu (Promise), kas tiek atrisināts, kad tiek atrasts atbilstošais elements. Tie ir noderīgi, testējot asinhronas darbības, kas izraisa izmaiņas DOM.
"Hooks" testēšana
React "Hooks" ir atkārtoti lietojamas funkcijas, kas iekapsulē stāvokļa loģiku. RTL nodrošina `renderHook` utilītu no `@testing-library/react-hooks` (kas ir novecojusi un aizstāta ar `@testing-library/react` tieši no v17), lai testētu pielāgotus "Hooks" izolēti.
// src/hooks/useCounter.js
import { useState } from 'react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => {
setCount(prevCount => prevCount + 1);
};
const decrement = () => {
setCount(prevCount => prevCount - 1);
};
return { count, increment, decrement };
}
export default useCounter;
// src/hooks/useCounter.test.js
import { renderHook, act } from '@testing-library/react';
import useCounter from './useCounter';
test('increments the counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
Skaidrojums:
- `renderHook`: Šī funkcija renderē "Hook" un atgriež objektu, kas satur "Hook" rezultātu.
- `act`: Šī funkcija tiek izmantota, lai ietītu jebkuru kodu, kas izraisa stāvokļa atjauninājumus. Tas nodrošina, ka React var pareizi apvienot un apstrādāt atjauninājumus.
Padziļinātas testēšanas tehnikas
Kad esat apguvis RTL pamatus, varat izpētīt progresīvākas testēšanas tehnikas, lai uzlabotu savu testu kvalitāti un uzturamību.
Moduļu imitēšana (Mocking)
Dažreiz jums var būt nepieciešams imitēt ārējus moduļus vai atkarības, lai izolētu savus komponentus un kontrolētu to uzvedību testēšanas laikā. Jest nodrošina spēcīgu imitēšanas API šim nolūkam.
// src/api/dataService.js
export const fetchData = async () => {
const response = await fetch('/api/data');
const data = await response.json();
return data;
};
// src/components/MyComponent.js
import React, { useState, useEffect } from 'react';
import { fetchData } from '../api/dataService';
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
return <div>{data}</div>;
}
// src/components/MyComponent.test.js
import { render, screen, waitFor } from '@testing-library/react';
import MyComponent from './MyComponent';
import * as dataService from '../api/dataService';
jest.mock('../api/dataService');
test('fetches data from the API', async () => {
dataService.fetchData.mockResolvedValue({ message: 'Mocked data!' });
render(<MyComponent />);
await waitFor(() => screen.getByText('Mocked data!'));
expect(screen.getByText('Mocked data!')).toBeInTheDocument();
expect(dataService.fetchData).toHaveBeenCalledTimes(1);
});
Skaidrojums:
- `jest.mock('../api/dataService')`: Šī rinda imitē `dataService` moduli.
- `dataService.fetchData.mockResolvedValue({ message: 'Mocked data!' })`: Šī rinda konfigurē imitēto `fetchData` funkciju, lai tā atgrieztu solījumu, kas atrisinās ar norādītajiem datiem.
- `expect(dataService.fetchData).toHaveBeenCalledTimes(1)`: Šī rinda apgalvo, ka imitētā `fetchData` funkcija tika izsaukta vienu reizi.
Konteksta nodrošinātāji (Context Providers)
Ja jūsu komponents paļaujas uz Konteksta nodrošinātāju (Context Provider), jums testēšanas laikā būs jāietin jūsu komponents šajā nodrošinātājā. Tas nodrošina, ka komponentam ir piekļuve konteksta vērtībām.
// src/contexts/ThemeContext.js
import React, { createContext, useState } from 'react';
export const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// src/components/MyComponent.js
import React, { useContext } from 'react';
import { ThemeContext } from '../contexts/ThemeContext';
function MyComponent() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div style={{ backgroundColor: theme === 'light' ? '#fff' : '#000', color: theme === 'light' ? '#000' : '#fff' }}>
<p>Current theme: {theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}
// src/components/MyComponent.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';
import { ThemeProvider } from '../contexts/ThemeContext';
test('toggles the theme', () => {
render(
<ThemeProvider>
<MyComponent />
</ThemeProvider>
);
const themeParagraph = screen.getByText(/Current theme: light/i);
const toggleButton = screen.getByRole('button', { name: /Toggle Theme/i });
expect(themeParagraph).toBeInTheDocument();
fireEvent.click(toggleButton);
expect(screen.getByText(/Current theme: dark/i)).toBeInTheDocument();
});
Skaidrojums:
- Mēs ietinam `MyComponent` `ThemeProvider`, lai nodrošinātu nepieciešamo kontekstu testēšanas laikā.
Testēšana ar maršrutētāju (Router)
Testējot komponentus, kas izmanto React Router, jums būs jānodrošina imitēts maršrutētāja konteksts. To var panākt, izmantojot `MemoryRouter` komponentu no `react-router-dom`.
// src/components/MyComponent.js
import React from 'react';
import { Link } from 'react-router-dom';
function MyComponent() {
return (
<div>
<Link to="/about">Go to About Page</Link>
</div>
);
}
// src/components/MyComponent.test.js
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import MyComponent from './MyComponent';
test('renders a link to the about page', () => {
render(
<MemoryRouter>
<MyComponent />
</MemoryRouter>
);
const linkElement = screen.getByRole('link', { name: /Go to About Page/i });
expect(linkElement).toBeInTheDocument();
expect(linkElement).toHaveAttribute('href', '/about');
});
Skaidrojums:
- Mēs ietinam `MyComponent` `MemoryRouter`, lai nodrošinātu imitētu maršrutētāja kontekstu.
- Mēs apgalvojam, ka saites elementam ir pareizs `href` atribūts.
Labākās prakses efektīvu testu rakstīšanai
Šeit ir dažas labākās prakses, kas jāievēro, rakstot testus ar RTL:
- Koncentrējieties uz lietotāju mijiedarbībām: Rakstiet testus, kas simulē, kā lietotāji mijiedarbojas ar jūsu lietotni.
- Izvairieties no implementācijas detaļu testēšanas: Netestējiet savu komponentu iekšējo darbību. Tā vietā koncentrējieties uz novērojamo uzvedību.
- Rakstiet skaidrus un kodolīgus testus: Padariet savus testus viegli saprotamus un uzturamus.
- Izmantojiet jēgpilnus testu nosaukumus: Izvēlieties testu nosaukumus, kas precīzi apraksta testējamo uzvedību.
- Uzturiet testus izolētus: Izvairieties no atkarībām starp testiem. Katram testam jābūt neatkarīgam un pašpietiekamam.
- Testējiet robežgadījumus: Netestējiet tikai "laimīgo ceļu". Pārliecinieties, ka testējat arī robežgadījumus un kļūdu nosacījumus.
- Rakstiet testus pirms koda: Apsveriet iespēju izmantot testu vadītu izstrādi (TDD), lai rakstītu testus, pirms rakstāt kodu.
- Sekojiet "AAA" modelim: Arrange, Act, Assert (Sagatavot, Rīkoties, Apgalvot). Šis modelis palīdz strukturēt jūsu testus un padarīt tos lasāmākus.
- Uzturiet savus testus ātrus: Lēni testi var atturēt izstrādātājus no to biežas palaišanas. Optimizējiet savu testu ātrumu, imitējot tīkla pieprasījumus un minimizējot DOM manipulāciju apjomu.
- Izmantojiet aprakstošus kļūdu ziņojumus: Kad apgalvojumi neizdodas, kļūdu ziņojumiem jānodrošina pietiekami daudz informācijas, lai ātri identificētu neveiksmes cēloni.
Noslēgums
React Testing Library ir spēcīgs rīks, lai rakstītu efektīvus, uzturamus un uz lietotāju orientētus testus jūsu React lietotnēm. Sekojot šajā ceļvedī izklāstītajiem principiem un tehnikām, jūs varat veidot robustas un uzticamas lietotnes, kas atbilst jūsu lietotāju vajadzībām. Atcerieties koncentrēties uz testēšanu no lietotāja perspektīvas, izvairīties no implementācijas detaļu testēšanas un rakstīt skaidrus un kodolīgus testus. Pieņemot RTL un labākās prakses, jūs varat ievērojami uzlabot savu React projektu kvalitāti un uzturamību, neatkarīgi no jūsu atrašanās vietas vai jūsu globālās auditorijas īpašajām prasībām.