Osvojte si React Testing Library (RTL) s týmto kompletným sprievodcom. Naučte sa písať efektívne, udržateľné a na používateľa zamerané testy pre vaše React aplikácie.
React Testing Library: Komplexný sprievodca
V dnešnom rýchlo sa rozvíjajúcom svete webového vývoja je prvoradé zabezpečenie kvality a spoľahlivosti vašich React aplikácií. React Testing Library (RTL) sa stala populárnym a efektívnym riešením na písanie testov, ktoré sa zameriavajú na perspektívu používateľa. Tento sprievodca poskytuje kompletný prehľad RTL, pokrývajúci všetko od základných konceptov až po pokročilé techniky, a umožní vám tak vytvárať robustné a udržateľné React aplikácie.
Prečo si vybrať React Testing Library?
Tradičné prístupy k testovaniu sa často spoliehajú na detaily implementácie, čo robí testy krehkými a náchylnými na zlyhanie pri menších zmenách v kóde. RTL, na druhej strane, vás povzbudzuje k testovaniu komponentov tak, ako by s nimi interagoval používateľ, pričom sa zameriava na to, čo používateľ vidí a zažíva. Tento prístup ponúka niekoľko kľúčových výhod:
- Testovanie zamerané na používateľa: RTL podporuje písanie testov, ktoré odrážajú perspektívu používateľa, čím sa zaisťuje, že vaša aplikácia funguje podľa očakávaní z pohľadu koncového používateľa.
- Znížená krehkosť testov: Vďaka tomu, že sa vyhýbame testovaniu detailov implementácie, testy RTL majú menšiu pravdepodobnosť zlyhania pri refaktorovaní kódu, čo vedie k udržateľnejším a robustnejším testom.
- Zlepšený návrh kódu: RTL vás povzbudzuje k písaniu komponentov, ktoré sú prístupné a ľahko použiteľné, čo vedie k lepšiemu celkovému návrhu kódu.
- Dôraz na prístupnosť: RTL uľahčuje testovanie prístupnosti vašich komponentov, čím zaisťuje, že vaša aplikácia je použiteľná pre každého.
- Zjednodušený proces testovania: RTL poskytuje jednoduché a intuitívne API, čo uľahčuje písanie a údržbu testov.
Nastavenie vášho testovacieho prostredia
Predtým, ako začnete používať RTL, musíte si nastaviť testovacie prostredie. To zvyčajne zahŕňa inštaláciu potrebných závislostí a konfiguráciu vášho testovacieho frameworku.
Predpoklady
- Node.js a npm (alebo yarn): Uistite sa, že máte vo svojom systéme nainštalovaný Node.js a npm (alebo yarn). Môžete si ich stiahnuť z oficiálnej webovej stránky Node.js.
- React projekt: Mali by ste mať existujúci React projekt alebo si vytvoriť nový pomocou Create React App alebo podobného nástroja.
Inštalácia
Nainštalujte nasledujúce balíčky pomocou npm alebo yarn:
npm install --save-dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Alebo pomocou yarn:
yarn add --dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Vysvetlenie balíčkov:
- @testing-library/react: Hlavná knižnica na testovanie React komponentov.
- @testing-library/jest-dom: Poskytuje vlastné Jest matchers na overovanie DOM uzlov.
- Jest: Populárny JavaScript testovací framework.
- babel-jest: Jest transformer, ktorý používa Babel na kompiláciu vášho kódu.
- @babel/preset-env: Babel preset, ktorý určuje Babel pluginy a presety potrebné na podporu vašich cieľových prostredí.
- @babel/preset-react: Babel preset pre React.
Konfigurácia
Vytvorte súbor `babel.config.js` v koreňovom adresári vášho projektu s nasledujúcim obsahom:
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};
Aktualizujte svoj súbor `package.json` tak, aby obsahoval testovací skript:
{
"scripts": {
"test": "jest"
}
}
Vytvorte súbor `jest.config.js` v koreňovom adresári vášho projektu na konfiguráciu Jestu. Minimálna konfigurácia môže vyzerať takto:
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['/src/setupTests.js'],
};
Vytvorte súbor `src/setupTests.js` s nasledujúcim obsahom. Tým sa zabezpečí, že Jest DOM matchers budú dostupné vo všetkých vašich testoch:
import '@testing-library/jest-dom/extend-expect';
Napísanie vášho prvého testu
Začnime jednoduchým príkladom. Predpokladajme, že máte React komponent, ktorý zobrazuje uvítaciu správu:
// src/components/Greeting.js
import React from 'react';
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
export default Greeting;
Teraz napíšeme test pre tento komponent:
// 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();
});
Vysvetlenie:
- `render`: Táto funkcia vykreslí komponent do DOM.
- `screen`: Tento objekt poskytuje metódy na dopytovanie sa v DOM.
- `getByText`: Táto metóda nájde prvok podľa jeho textového obsahu. Príznak `/i` robí vyhľadávanie nezávislým od veľkosti písmen.
- `expect`: Táto funkcia sa používa na vytváranie tvrdení o správaní komponentu.
- `toBeInTheDocument`: Tento matcher overuje, že prvok je prítomný v DOM.
Ak chcete spustiť test, vykonajte nasledujúci príkaz v termináli:
npm test
Ak je všetko správne nakonfigurované, test by mal prejsť.
Bežné RTL dopyty (Queries)
RTL poskytuje rôzne metódy dopytovania na nájdenie prvkov v DOM. Tieto dopyty sú navrhnuté tak, aby napodobňovali interakciu používateľov s vašou aplikáciou.
`getByRole`
Tento dopyt nájde prvok podľa jeho ARIA role. Je dobrým zvykom používať `getByRole` kedykoľvek je to možné, pretože to podporuje prístupnosť a zaisťuje, že vaše testy sú odolné voči zmenám v základnej štruktúre DOM.
<button role="button">Click me</button>
const buttonElement = screen.getByRole('button');
expect(buttonElement).toBeInTheDocument();
`getByLabelText`
Tento dopyt nájde prvok podľa textu jeho priradeného labelu (štítku). Je to užitočné na testovanie prvkov formulára.
<label htmlFor="name">Name:</label>
<input type="text" id="name" />
const nameInputElement = screen.getByLabelText('Name:');
expect(nameInputElement).toBeInTheDocument();
`getByPlaceholderText`
Tento dopyt nájde prvok podľa jeho placeholder textu.
<input type="text" placeholder="Enter your email" />
const emailInputElement = screen.getByPlaceholderText('Enter your email');
expect(emailInputElement).toBeInTheDocument();
`getByAltText`
Tento dopyt nájde obrázkový prvok podľa jeho alt textu. Je dôležité poskytnúť zmysluplný alt text pre všetky obrázky, aby sa zabezpečila prístupnosť.
<img src="logo.png" alt="Company Logo" />
const logoImageElement = screen.getByAltText('Company Logo');
expect(logoImageElement).toBeInTheDocument();
`getByTitle`
Tento dopyt nájde prvok podľa jeho atribútu title.
<span title="Close">X</span>
const closeElement = screen.getByTitle('Close');
expect(closeElement).toBeInTheDocument();
`getByDisplayValue`
Tento dopyt nájde prvok podľa jeho zobrazenej hodnoty. Je to užitočné na testovanie vstupných polí formulára s predvyplnenými hodnotami.
<input type="text" value="Initial Value" />
const inputElement = screen.getByDisplayValue('Initial Value');
expect(inputElement).toBeInTheDocument();
Dopyty `getAllBy*`
Okrem dopytov `getBy*` poskytuje RTL aj dopyty `getAllBy*`, ktoré vracajú pole zhodných prvkov. Sú užitočné, keď potrebujete overiť, že v DOM je prítomných viacero prvkov s rovnakými vlastnosťami.
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
const listItems = screen.getAllByRole('listitem');
expect(listItems).toHaveLength(3);
Dopyty `queryBy*`
Dopyty `queryBy*` sú podobné dopytom `getBy*`, ale vracajú `null`, ak sa nenájde žiadny zhodný prvok, namiesto toho, aby vyhodili chybu. To je užitočné, keď chcete overiť, že prvok *nie je* prítomný v DOM.
const missingElement = screen.queryByText('Non-existent text');
expect(missingElement).toBeNull();
Dopyty `findBy*`
Dopyty `findBy*` sú asynchrónne verzie dopytov `getBy*`. Vracajú Promise, ktorý sa vyrieši, keď sa nájde zhodný prvok. Sú užitočné na testovanie asynchrónnych operácií, ako je načítavanie údajov z API.
// Simulácia asynchrónneho načítania údajov
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('načíta dáta asynchrónne', async () => {
render(<MyComponent />);
const dataElement = await screen.findByText('Data Loaded!');
expect(dataElement).toBeInTheDocument();
});
Simulácia interakcií používateľa
RTL poskytuje API `fireEvent` a `userEvent` na simuláciu interakcií používateľa, ako je klikanie na tlačidlá, písanie do vstupných polí a odosielanie formulárov.
`fireEvent`
`fireEvent` vám umožňuje programovo spúšťať DOM udalosti. Je to nízkoúrovňové API, ktoré vám dáva jemnú kontrolu nad spúšťanými udalosťami.
<button onClick={() => alert('Button clicked!')}>Click me</button>
import { fireEvent } from '@testing-library/react';
test('simuluje kliknutie na tlačidlo', () => {
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` je vysokoúrovňové API, ktoré simuluje interakcie používateľa realistickejšie. Stará sa o detaily, ako je správa focusu a poradie udalostí, vďaka čomu sú vaše testy robustnejšie a menej krehké.
<input type="text" onChange={e => console.log(e.target.value)} />
import userEvent from '@testing-library/user-event';
test('simuluje písanie do vstupného poľa', () => {
const inputElement = screen.getByRole('textbox');
userEvent.type(inputElement, 'Hello, world!');
expect(inputElement).toHaveValue('Hello, world!');
});
Testovanie asynchrónneho kódu
Mnoho React aplikácií zahŕňa asynchrónne operácie, ako je načítavanie údajov z API. RTL poskytuje niekoľko nástrojov na testovanie asynchrónneho kódu.
`waitFor`
`waitFor` vám umožňuje počkať, kým sa nejaká podmienka stane pravdivou, predtým ako urobíte tvrdenie. Je to užitočné na testovanie asynchrónnych operácií, ktoré potrebujú nejaký čas na dokončenie.
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('čaká na načítanie dát', async () => {
render(<MyComponent />);
await waitFor(() => screen.getByText('Data loaded!'));
const dataElement = screen.getByText('Data loaded!');
expect(dataElement).toBeInTheDocument();
});
Dopyty `findBy*`
Ako už bolo spomenuté, dopyty `findBy*` sú asynchrónne a vracajú Promise, ktorý sa vyrieši, keď sa nájde zhodný prvok. Sú užitočné na testovanie asynchrónnych operácií, ktoré vedú k zmenám v DOM.
Testovanie Hooks
React Hooks sú znovupoužiteľné funkcie, ktoré zapuzdrujú logiku so stavom. RTL poskytuje utilitu `renderHook` z `@testing-library/react-hooks` (ktorá je od verzie v17 zastaraná v prospech priameho použitia `@testing-library/react`) na testovanie vlastných Hooks v izolácii.
// 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('inkrementuje počítadlo', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
Vysvetlenie:
- `renderHook`: Táto funkcia vykreslí Hook a vráti objekt obsahujúci výsledok Hooku.
- `act`: Táto funkcia sa používa na zabalenie akéhokoľvek kódu, ktorý spôsobuje aktualizácie stavu. Tým sa zabezpečí, že React dokáže správne dávkovať a spracovať aktualizácie.
Pokročilé techniky testovania
Keď si osvojíte základy RTL, môžete preskúmať pokročilejšie techniky testovania na zlepšenie kvality a udržateľnosti vašich testov.
Mockovanie modulov
Niekedy môže byť potrebné mockovať externé moduly alebo závislosti, aby ste izolovali svoje komponenty a kontrolovali ich správanie počas testovania. Jest na tento účel poskytuje výkonné API na mockovanie.
// 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('načítava dáta z 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);
});
Vysvetlenie:
- `jest.mock('../api/dataService')`: Tento riadok mockuje modul `dataService`.
- `dataService.fetchData.mockResolvedValue({ message: 'Mocked data!' })`: Tento riadok konfiguruje mockovanú funkciu `fetchData` tak, aby vracala Promise, ktorý sa vyrieši so zadanými dátami.
- `expect(dataService.fetchData).toHaveBeenCalledTimes(1)`: Tento riadok overuje, že mockovaná funkcia `fetchData` bola zavolaná raz.
Context Providers
Ak sa váš komponent spolieha na Context Provider, budete musieť počas testovania zabaliť váš komponent do providera. Tým sa zabezpečí, že komponent bude mať prístup k hodnotám z kontextu.
// 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('prepína tému', () => {
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();
});
Vysvetlenie:
- Komponent `MyComponent` zabalíme do `ThemeProvider`, aby sme počas testovania poskytli potrebný kontext.
Testovanie s Routerom
Pri testovaní komponentov, ktoré používajú React Router, budete musieť poskytnúť mockovaný kontext Routera. To môžete dosiahnuť pomocou komponentu `MemoryRouter` z `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('vykreslí odkaz na stránku o nás', () => {
render(
<MemoryRouter>
<MyComponent />
</MemoryRouter>
);
const linkElement = screen.getByRole('link', { name: /Go to About Page/i });
expect(linkElement).toBeInTheDocument();
expect(linkElement).toHaveAttribute('href', '/about');
});
Vysvetlenie:
- Komponent `MyComponent` zabalíme do `MemoryRouter`, aby sme poskytli mockovaný kontext Routera.
- Overujeme, že prvok odkazu má správny atribút `href`.
Osvedčené postupy pre písanie efektívnych testov
Tu je niekoľko osvedčených postupov, ktoré treba dodržiavať pri písaní testov s RTL:
- Zamerajte sa na interakcie používateľa: Píšte testy, ktoré simulujú, ako používatelia interagujú s vašou aplikáciou.
- Vyhnite sa testovaniu detailov implementácie: Netestujte vnútorné fungovanie vašich komponentov. Namiesto toho sa zamerajte na pozorovateľné správanie.
- Píšte jasné a stručné testy: Urobte svoje testy ľahko zrozumiteľnými a udržateľnými.
- Používajte zmysluplné názvy testov: Vyberajte názvy testov, ktoré presne popisujú testované správanie.
- Udržujte testy izolované: Vyhnite sa závislostiam medzi testami. Každý test by mal byť nezávislý a sebestačný.
- Testujte okrajové prípady: Netestujte len "happy path". Uistite sa, že testujete aj okrajové prípady a chybové stavy.
- Píšte testy pred kódom: Zvážte použitie Test-Driven Development (TDD) na písanie testov predtým, ako napíšete kód.
- Dodržiavajte vzor "AAA": Arrange, Act, Assert (Pripraviť, Vykonať, Overiť). Tento vzor pomáha štruktúrovať vaše testy a robí ich čitateľnejšími.
- Udržujte svoje testy rýchle: Pomalé testy môžu odradiť vývojárov od ich častého spúšťania. Optimalizujte svoje testy na rýchlosť mockovaním sieťových požiadaviek a minimalizáciou množstva manipulácie s DOM.
- Používajte popisné chybové hlásenia: Keď tvrdenia zlyhajú, chybové hlásenia by mali poskytnúť dostatok informácií na rýchle identifikovanie príčiny zlyhania.
Záver
React Testing Library je výkonný nástroj na písanie efektívnych, udržateľných a na používateľa zameraných testov pre vaše React aplikácie. Dodržiavaním princípov a techník uvedených v tomto sprievodcovi môžete vytvárať robustné a spoľahlivé aplikácie, ktoré spĺňajú potreby vašich používateľov. Nezabudnite sa zamerať na testovanie z pohľadu používateľa, vyhýbať sa testovaniu detailov implementácie a písať jasné a stručné testy. Osvojením si RTL a prijatím osvedčených postupov môžete výrazne zlepšiť kvalitu a udržateľnosť vašich React projektov, bez ohľadu na vašu lokalitu alebo špecifické požiadavky vášho globálneho publika.