Įsisavinkite „React Testing Library“ (RTL) su šiuo išsamiu vadovu. Išmokite rašyti efektyvius, palaikomus ir į vartotoją orientuotus testus savo „React“ programoms, sutelkiant dėmesį į geriausias praktikas ir realius pavyzdžius.
React Testing Library: išsamus vadovas
Šiuolaikinėje sparčiai kintančioje interneto svetainių kūrimo aplinkoje itin svarbu užtikrinti „React“ programų kokybę ir patikimumą. „React Testing Library“ (RTL) tapo populiariu ir efektyviu sprendimu rašyti testus, kurie orientuoti į vartotojo perspektyvą. Šis vadovas pateikia išsamią RTL apžvalgą, apimančią viską nuo pagrindinių koncepcijų iki pažangių technikų, ir suteikia jums galimybę kurti patikimas ir lengvai palaikomas „React“ programas.
Kodėl verta rinktis „React Testing Library“?
Tradiciniai testavimo metodai dažnai remiasi įgyvendinimo detalėmis, todėl testai tampa trapūs ir linkę lūžti po menkiausių kodo pakeitimų. Kita vertus, RTL skatina testuoti komponentus taip, kaip su jais sąveikautų vartotojas, sutelkiant dėmesį į tai, ką vartotojas mato ir patiria. Šis požiūris suteikia keletą svarbių pranašumų:
- Į vartotoją orientuotas testavimas: RTL skatina rašyti testus, kurie atspindi vartotojo perspektyvą, užtikrinant, kad jūsų programa veiktų taip, kaip tikimasi iš galutinio vartotojo požiūrio taško.
- Sumažėjęs testų trapumas: Vengiant testuoti įgyvendinimo detales, RTL testai mažiau linkę lūžti refaktorizuojant kodą, o tai lemia lengviau palaikomus ir patikimesnius testus.
- Geresnis kodo dizainas: RTL skatina rašyti komponentus, kurie yra prieinami ir lengvai naudojami, o tai lemia geresnį bendrą kodo dizainą.
- Dėmesys prieinamumui: RTL palengvina komponentų prieinamumo testavimą, užtikrinant, kad jūsų programa būtų naudojama visiems.
- Supaprastintas testavimo procesas: RTL suteikia paprastą ir intuityvią API, todėl testus rašyti ir prižiūrėti yra lengviau.
Testavimo aplinkos paruošimas
Prieš pradedant naudoti RTL, turite paruošti savo testavimo aplinką. Paprastai tai apima būtinų priklausomybių diegimą ir testavimo karkaso konfigūravimą.
Būtinosios sąlygos
- Node.js ir npm (arba yarn): Įsitikinkite, kad jūsų sistemoje yra įdiegti Node.js ir npm (arba yarn). Juos galite atsisiųsti iš oficialios Node.js svetainės.
- React projektas: Turėtumėte turėti esamą „React“ projektą arba susikurti naują naudodami „Create React App“ ar panašų įrankį.
Diegimas
Įdiekite šiuos paketus naudodami npm arba yarn:
npm install --save-dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Arba, naudojant yarn:
yarn add --dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Paketų paaiškinimas:
- @testing-library/react: Pagrindinė biblioteka „React“ komponentams testuoti.
- @testing-library/jest-dom: Suteikia pasirinktinius „Jest“ derintojus (matchers) DOM mazgų tvirtinimui.
- Jest: Populiarus „JavaScript“ testavimo karkasas.
- babel-jest: „Jest“ transformatorius, kuris naudoja „Babel“ jūsų kodui kompiliuoti.
- @babel/preset-env: „Babel“ išankstinis nustatymas, kuris nustato „Babel“ papildinius ir išankstinius nustatymus, reikalingus jūsų tikslinėms aplinkoms palaikyti.
- @babel/preset-react: „Babel“ išankstinis nustatymas, skirtas „React“.
Konfigūracija
Projekto šakninėje direktorijoje sukurkite `babel.config.js` failą su šiuo turiniu:
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};
Atnaujinkite savo `package.json` failą, įtraukdami testavimo scenarijų:
{
"scripts": {
"test": "jest"
}
}
Projekto šakninėje direktorijoje sukurkite `jest.config.js` failą, kad sukonfigūruotumėte „Jest“. Minimali konfigūracija galėtų atrodyti taip:
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['/src/setupTests.js'],
};
Sukurkite `src/setupTests.js` failą su šiuo turiniu. Tai užtikrins, kad „Jest DOM“ derintojai (matchers) bus prieinami visuose jūsų testuose:
import '@testing-library/jest-dom/extend-expect';
Pirmojo testo rašymas
Pradėkime nuo paprasto pavyzdžio. Tarkime, turite „React“ komponentą, kuris rodo pasveikinimo pranešimą:
// src/components/Greeting.js
import React from 'react';
function Greeting({ name }) {
return <h1>Sveiki, {name}!</h1>;
}
export default Greeting;
Dabar parašykime šio komponento testą:
// src/components/Greeting.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import Greeting from './Greeting';
test('atvaizduoja pasveikinimo pranešimą', () => {
render(<Greeting name="Pasauli" />);
const greetingElement = screen.getByText(/Sveiki, Pasauli!/i);
expect(greetingElement).toBeInTheDocument();
});
Paaiškinimas:
- `render`: Ši funkcija atvaizduoja komponentą į DOM.
- `screen`: Šis objektas suteikia metodus DOM užklausoms.
- `getByText`: Šis metodas suranda elementą pagal jo teksto turinį. `/i` vėliavėlė paiešką padaro nejautrią raidžių dydžiui.
- `expect`: Ši funkcija naudojama tvirtinimams apie komponento elgseną atlikti.
- `toBeInTheDocument`: Šis derintojas (matcher) patvirtina, kad elementas yra DOM.
Norėdami paleisti testą, terminale įvykdykite šią komandą:
npm test
Jei viskas sukonfigūruota teisingai, testas turėtų būti sėkmingas.
Įprastos RTL užklausos
RTL pateikia įvairius užklausų metodus elementams DOM'e rasti. Šios užklausos sukurtos imituoti, kaip vartotojai sąveikauja su jūsų programa.
`getByRole`
Ši užklausa suranda elementą pagal jo ARIA rolę. Gera praktika yra naudoti `getByRole` kai tik įmanoma, nes tai skatina prieinamumą ir užtikrina, kad jūsų testai būtų atsparūs pokyčiams pagrindinėje DOM struktūroje.
<button role="button">Spausk mane</button>
const buttonElement = screen.getByRole('button');
expect(buttonElement).toBeInTheDocument();
`getByLabelText`
Ši užklausa suranda elementą pagal susijusios etiketės (label) tekstą. Tai naudinga testuojant formos elementus.
<label htmlFor="name">Vardas:</label>
<input type="text" id="name" />
const nameInputElement = screen.getByLabelText('Vardas:');
expect(nameInputElement).toBeInTheDocument();
`getByPlaceholderText`
Ši užklausa suranda elementą pagal jo rezervuotos vietos (placeholder) tekstą.
<input type="text" placeholder="Įveskite savo el. paštą" />
const emailInputElement = screen.getByPlaceholderText('Įveskite savo el. paštą');
expect(emailInputElement).toBeInTheDocument();
`getByAltText`
Ši užklausa suranda paveikslėlio elementą pagal jo alternatyvų (alt) tekstą. Svarbu pateikti prasmingą alternatyvų tekstą visiems paveikslėliams, siekiant užtikrinti prieinamumą.
<img src="logo.png" alt="Įmonės logotipas" />
const logoImageElement = screen.getByAltText('Įmonės logotipas');
expect(logoImageElement).toBeInTheDocument();
`getByTitle`
Ši užklausa suranda elementą pagal jo pavadinimo (title) atributą.
<span title="Uždaryti">X</span>
const closeElement = screen.getByTitle('Uždaryti');
expect(closeElement).toBeInTheDocument();
`getByDisplayValue`
Ši užklausa suranda elementą pagal jo rodomą reikšmę. Tai naudinga testuojant formos įvesties laukus su iš anksto užpildytomis reikšmėmis.
<input type="text" value="Pradinė reikšmė" />
const inputElement = screen.getByDisplayValue('Pradinė reikšmė');
expect(inputElement).toBeInTheDocument();
`getAllBy*` užklausos
Be `getBy*` užklausų, RTL taip pat teikia `getAllBy*` užklausas, kurios grąžina atitinkančių elementų masyvą. Tai naudinga, kai reikia patvirtinti, kad DOM'e yra keli elementai su tomis pačiomis savybėmis.
<li>Punktas 1</li>
<li>Punktas 2</li>
<li>Punktas 3</li>
const listItems = screen.getAllByRole('listitem');
expect(listItems).toHaveLength(3);
`queryBy*` užklausos
`queryBy*` užklausos yra panašios į `getBy*` užklausas, tačiau jos grąžina `null`, jei nerandamas atitinkamas elementas, užuot išmetus klaidą. Tai naudinga, kai norite patvirtinti, kad elemento *nėra* DOM'e.
const missingElement = screen.queryByText('Neegzistuojantis tekstas');
expect(missingElement).toBeNull();
`findBy*` užklausos
`findBy*` užklausos yra asinchroninės `getBy*` užklausų versijos. Jos grąžina `Promise`, kuris išsipildo, kai randamas atitinkamas elementas. Jos naudingos testuojant asinchronines operacijas, pvz., duomenų gavimą iš API.
// Imituojamas asinchroninis duomenų gavimas
const fetchData = () => new Promise(resolve => {
setTimeout(() => resolve('Duomenys įkelti!'), 1000);
});
function MyComponent() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
fetchData().then(setData);
}, []);
return <div>{data}</div>;
}
test('įkelia duomenis asinchroniškai', async () => {
render(<MyComponent />);
const dataElement = await screen.findByText('Duomenys įkelti!');
expect(dataElement).toBeInTheDocument();
});
Vartotojo sąveikų imitavimas
RTL pateikia `fireEvent` ir `userEvent` API, skirtas imituoti vartotojo sąveikas, pvz., mygtukų paspaudimus, teksto įvedimą į laukelius ir formų pateikimą.
`fireEvent`
`fireEvent` leidžia programiškai sukelti DOM įvykius. Tai žemesnio lygio API, suteikianti smulkiagrūdę kontrolę pār sukeliamus įvykius.
<button onClick={() => alert('Mygtukas paspaustas!')}>Spausk mane</button>
import { fireEvent } from '@testing-library/react';
test('imituoja mygtuko paspaudimą', () => {
const alertMock = jest.spyOn(window, 'alert').mockImplementation(() => {});
render(<button onClick={() => alert('Mygtukas paspaustas!')}>Spausk mane</button>);
const buttonElement = screen.getByRole('button');
fireEvent.click(buttonElement);
expect(alertMock).toHaveBeenCalledTimes(1);
alertMock.mockRestore();
});
`userEvent`
`userEvent` yra aukštesnio lygio API, kuri imituoja vartotojo sąveikas realistiškiau. Ji tvarko tokias detales kaip fokusavimo valdymas ir įvykių eiliškumas, todėl jūsų testai tampa patikimesni ir mažiau trapūs.
<input type="text" onChange={e => console.log(e.target.value)} />
import userEvent from '@testing-library/user-event';
test('imituoja teksto įvedimą į laukelį', () => {
const inputElement = screen.getByRole('textbox');
userEvent.type(inputElement, 'Sveiki, pasauli!');
expect(inputElement).toHaveValue('Sveiki, pasauli!');
});
Asinchroninio kodo testavimas
Daugelis „React“ programų apima asinchronines operacijas, tokias kaip duomenų gavimas iš API. RTL pateikia keletą įrankių asinchroniniam kodui testuoti.
`waitFor`
`waitFor` leidžia laukti, kol tam tikra sąlyga taps teisinga, prieš atliekant tvirtinimą. Tai naudinga testuojant asinchronines operacijas, kurioms įvykdyti reikia šiek tiek laiko.
function MyComponent() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
setTimeout(() => {
setData('Duomenys įkelti!');
}, 1000);
}, []);
return <div>{data}</div>;
}
import { waitFor } from '@testing-library/react';
test('laukia, kol duomenys bus įkelti', async () => {
render(<MyComponent />);
await waitFor(() => screen.getByText('Duomenys įkelti!'));
const dataElement = screen.getByText('Duomenys įkelti!');
expect(dataElement).toBeInTheDocument();
});
`findBy*` užklausos
Kaip minėta anksčiau, `findBy*` užklausos yra asinchroninės ir grąžina `Promise`, kuris išsipildo, kai randamas atitinkamas elementas. Jos naudingos testuojant asinchronines operacijas, kurios sukelia pakeitimus DOM.
„Hooks“ testavimas
„React Hooks“ yra daugkartinio naudojimo funkcijos, kurios apgaubia būsenos logiką. RTL pateikia `renderHook` įrankį iš `@testing-library/react-hooks` (kuris nebenaudojamas nuo v17, pirmenybę teikiant `@testing-library/react` tiesiogiai), skirtą testuoti pasirinktinius „Hooks“ atskirai.
// 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('padidina skaitiklį', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
Paaiškinimas:
- `renderHook`: Ši funkcija atvaizduoja „Hook“ ir grąžina objektą, kuriame yra „Hook“ rezultatas.
- `act`: Ši funkcija naudojama apgaubti bet kokį kodą, kuris sukelia būsenos atnaujinimus. Tai užtikrina, kad „React“ gali tinkamai sugrupuoti ir apdoroti atnaujinimus.
Pažangios testavimo technikos
Įsisavinę RTL pagrindus, galite tyrinėti pažangesnes testavimo technikas, kad pagerintumėte savo testų kokybę ir palaikomumą.
Modulių imitavimas (mocking)
Kartais gali tekti imituoti išorinius modulius ar priklausomybes, kad išskirtumėte savo komponentus ir kontroliuotumėte jų elgseną testavimo metu. „Jest“ šiam tikslui suteikia galingą imitavimo API.
// 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('gauna duomenis iš API', async () => {
dataService.fetchData.mockResolvedValue({ message: 'Imituoti duomenys!' });
render(<MyComponent />);
await waitFor(() => screen.getByText('Imituoti duomenys!'));
expect(screen.getByText('Imituoti duomenys!')).toBeInTheDocument();
expect(dataService.fetchData).toHaveBeenCalledTimes(1);
});
Paaiškinimas:
- `jest.mock('../api/dataService')`: Ši eilutė imituoja `dataService` modulį.
- `dataService.fetchData.mockResolvedValue({ message: 'Imituoti duomenys!' })`: Ši eilutė konfigūruoja imituotą `fetchData` funkciją, kad ji grąžintų `Promise`, kuris išsipildo su nurodytais duomenimis.
- `expect(dataService.fetchData).toHaveBeenCalledTimes(1)`: Ši eilutė patvirtina, kad imituota `fetchData` funkcija buvo iškviesta vieną kartą.
Konteksto teikėjai (Context Providers)
Jei jūsų komponentas priklauso nuo konteksto teikėjo (Context Provider), testavimo metu turėsite apgaubti savo komponentą teikėju. Tai užtikrina, kad komponentas turės prieigą prie konteksto reikšmių.
// 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>Dabartinė tema: {theme}</p>
<button onClick={toggleTheme}>Keisti temą</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('perjungia temą', () => {
render(
<ThemeProvider>
<MyComponent />
</ThemeProvider>
);
const themeParagraph = screen.getByText(/Dabartinė tema: light/i);
const toggleButton = screen.getByRole('button', { name: /Keisti temą/i });
expect(themeParagraph).toBeInTheDocument();
fireEvent.click(toggleButton);
expect(screen.getByText(/Dabartinė tema: dark/i)).toBeInTheDocument();
});
Paaiškinimas:
- Mes apgaubiame `MyComponent` `ThemeProvider` komponentu, kad testavimo metu suteiktume reikiamą kontekstą.
Testavimas su „Router“
Testuojant komponentus, kurie naudoja „React Router“, reikės pateikti imituotą „Router“ kontekstą. Tai galite pasiekti naudodami `MemoryRouter` komponentą iš `react-router-dom`.
// src/components/MyComponent.js
import React from 'react';
import { Link } from 'react-router-dom';
function MyComponent() {
return (
<div>
<Link to="/about">Eiti į Apie puslapį</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('atvaizduoja nuorodą į apie puslapį', () => {
render(
<MemoryRouter>
<MyComponent />
</MemoryRouter>
);
const linkElement = screen.getByRole('link', { name: /Eiti į Apie puslapį/i });
expect(linkElement).toBeInTheDocument();
expect(linkElement).toHaveAttribute('href', '/about');
});
Paaiškinimas:
- Mes apgaubiame `MyComponent` `MemoryRouter` komponentu, kad suteiktume imituotą „Router“ kontekstą.
- Mes patvirtiname, kad nuorodos elementas turi teisingą `href` atributą.
Geriausios praktikos rašant efektyvius testus
Štai keletas geriausių praktikų, kurių reikėtų laikytis rašant testus su RTL:
- Sutelkite dėmesį į vartotojo sąveikas: Rašykite testus, kurie imituoja, kaip vartotojai sąveikauja su jūsų programa.
- Venkite testuoti įgyvendinimo detales: Netestuokite vidinio savo komponentų veikimo. Vietoj to, sutelkite dėmesį į stebimą elgseną.
- Rašykite aiškius ir glaustus testus: Padarykite savo testus lengvai suprantamus ir palaikomus.
- Naudokite prasmingus testų pavadinimus: Pasirinkite testų pavadinimus, kurie tiksliai apibūdina testuojamą elgseną.
- Laikykite testus izoliuotus: Venkite priklausomybių tarp testų. Kiekvienas testas turėtų būti nepriklausomas ir autonomiškas.
- Testuokite kraštutinius atvejus: Netestuokite tik sėkmingo scenarijaus. Įsitikinkite, kad testuojate ir kraštutinius atvejus bei klaidų sąlygas.
- Rašykite testus prieš rašydami kodą: Apsvarstykite galimybę naudoti testais paremtą kūrimą (TDD), kad parašytumėte testus prieš rašydami kodą.
- Laikykitės „AAA“ modelio: Parengti, Veikti, Patvirtinti (Arrange, Act, Assert). Šis modelis padeda struktūrizuoti jūsų testus ir padaryti juos skaitomesnius.
- Užtikrinkite, kad jūsų testai būtų greiti: Lėti testai gali atgrasyti kūrėjus nuo dažno jų paleidimo. Optimizuokite savo testų greitį imituodami tinklo užklausas ir minimizuodami DOM manipuliacijų kiekį.
- Naudokite aprašomuosius klaidų pranešimus: Kai tvirtinimai nepavyksta, klaidų pranešimai turėtų suteikti pakankamai informacijos, kad būtų galima greitai nustatyti gedimo priežastį.
Išvados
„React Testing Library“ yra galingas įrankis, skirtas rašyti efektyvius, palaikomus ir į vartotoją orientuotus testus jūsų „React“ programoms. Laikydamiesi šiame vadove aprašytų principų ir technikų, galite kurti patikimas ir patikimas programas, atitinkančias jūsų vartotojų poreikius. Nepamirškite sutelkti dėmesį į testavimą iš vartotojo perspektyvos, vengti testuoti įgyvendinimo detales ir rašyti aiškius bei glaustus testus. Pasitelkdami RTL ir taikydami geriausias praktikas, galite žymiai pagerinti savo „React“ projektų kokybę ir palaikomumą, nepriklausomai nuo jūsų buvimo vietos ar specifinių jūsų pasaulinės auditorijos reikalavimų.