Obvladajte React Testing Library (RTL) s tem popolnim vodnikom. Naučite se pisati učinkovite, vzdržljive in uporabniško usmerjene teste za vaše aplikacije React.
React Testing Library: Celovit vodnik
V današnjem hitro razvijajočem se svetu spletnega razvoja je zagotavljanje kakovosti in zanesljivosti vaših aplikacij React ključnega pomena. React Testing Library (RTL) se je uveljavila kot priljubljena in učinkovita rešitev za pisanje testov, ki se osredotočajo na uporabniško perspektivo. Ta vodnik ponuja celovit pregled RTL, od temeljnih konceptov do naprednih tehnik, s katerim boste lahko gradili robustne in vzdržljive aplikacije React.
Zakaj izbrati React Testing Library?
Tradicionalni pristopi k testiranju se pogosto zanašajo na podrobnosti implementacije, zaradi česar so testi krhki in se zlahka zlomijo ob manjših spremembah kode. RTL pa vas spodbuja, da testirate svoje komponente tako, kot bi z njimi komuniciral uporabnik, s poudarkom na tem, kar uporabnik vidi in doživi. Ta pristop ponuja več ključnih prednosti:
- Uporabniško usmerjeno testiranje: RTL spodbuja pisanje testov, ki odražajo uporabnikovo perspektivo, s čimer zagotavlja, da vaša aplikacija deluje, kot je pričakovano z vidika končnega uporabnika.
- Zmanjšana krhkost testov: Z izogibanjem testiranju podrobnosti implementacije je manj verjetno, da se bodo testi RTL zlomili ob preoblikovanju kode, kar vodi do bolj vzdržljivih in robustnih testov.
- Izboljšan dizajn kode: RTL vas spodbuja k pisanju komponent, ki so dostopne in enostavne za uporabo, kar vodi do boljšega splošnega dizajna kode.
- Poudarek na dostopnosti: RTL olajša testiranje dostopnosti vaših komponent, kar zagotavlja, da je vaša aplikacija uporabna za vse.
- Poenostavljen postopek testiranja: RTL ponuja preprost in intuitiven API, kar olajša pisanje in vzdrževanje testov.
Nastavitev testnega okolja
Preden lahko začnete uporabljati RTL, morate nastaviti svoje testno okolje. To običajno vključuje namestitev potrebnih odvisnosti in konfiguracijo vašega testnega ogrodja.
Predpogoji
- Node.js in npm (ali yarn): Prepričajte se, da imate na svojem sistemu nameščena Node.js in npm (ali yarn). Prenesete ju lahko z uradne spletne strani Node.js.
- Projekt React: Imeti morate obstoječ projekt React ali ustvariti novega z uporabo Create React App ali podobnega orodja.
Namestitev
Namestite naslednje pakete z uporabo npm ali yarn:
npm install --save-dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Ali, z uporabo yarn:
yarn add --dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Pojasnilo paketov:
- @testing-library/react: Osrednja knjižnica za testiranje komponent React.
- @testing-library/jest-dom: Zagotavlja Jest primerjalnike (matcherje) po meri za preverjanje DOM vozlišč.
- Jest: Priljubljeno ogrodje za testiranje v JavaScriptu.
- babel-jest: Jest transformer, ki uporablja Babel za prevajanje vaše kode.
- @babel/preset-env: Babel prednastavitev, ki določa potrebne Babel vtičnike in prednastavitve za podporo vaših ciljnih okolij.
- @babel/preset-react: Babel prednastavitev za React.
Konfiguracija
Ustvarite datoteko `babel.config.js` v korenski mapi vašega projekta z naslednjo vsebino:
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};
Posodobite svojo datoteko `package.json`, da vključite skript za testiranje:
{
"scripts": {
"test": "jest"
}
}
Ustvarite datoteko `jest.config.js` v korenski mapi vašega projekta za konfiguracijo Jesta. Minimalna konfiguracija bi lahko izgledala takole:
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['/src/setupTests.js'],
};
Ustvarite datoteko `src/setupTests.js` z naslednjo vsebino. To zagotavlja, da so Jest DOM primerjalniki na voljo v vseh vaših testih:
import '@testing-library/jest-dom/extend-expect';
Pisanje prvega testa
Začnimo s preprostim primerom. Recimo, da imate komponento React, ki prikazuje pozdravno sporočilo:
// src/components/Greeting.js
import React from 'react';
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
export default Greeting;
Zdaj pa napišimo test za to komponento:
// 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();
});
Pojasnilo:
- `render`: Ta funkcija renderira komponento v DOM.
- `screen`: Ta objekt ponuja metode za poizvedovanje po DOM-u.
- `getByText`: Ta metoda najde element po njegovi besedilni vsebini. Zastavica `/i` naredi iskanje neodvisno od velikosti črk.
- `expect`: Ta funkcija se uporablja za postavljanje trditev (assertions) o obnašanju komponente.
- `toBeInTheDocument`: Ta primerjalnik (matcher) trdi, da je element prisoten v DOM-u.
Za zagon testa v terminalu izvedite naslednji ukaz:
npm test
Če je vse pravilno nastavljeno, bi moral test uspeti.
Pogoste RTL poizvedbe
RTL ponuja različne metode poizvedb za iskanje elementov v DOM-u. Te poizvedbe so zasnovane tako, da posnemajo, kako uporabniki komunicirajo z vašo aplikacijo.
`getByRole`
Ta poizvedba najde element po njegovi ARIA vlogi. Dobra praksa je, da uporabljate `getByRole` kadar koli je to mogoče, saj spodbuja dostopnost in zagotavlja, da so vaši testi odporni na spremembe v osnovni strukturi DOM-a.
<button role="button">Click me</button>
const buttonElement = screen.getByRole('button');
expect(buttonElement).toBeInTheDocument();
`getByLabelText`
Ta poizvedba najde element po besedilu pripadajoče oznake. Uporabna je za testiranje elementov obrazcev.
<label htmlFor="name">Name:</label>
<input type="text" id="name" />
const nameInputElement = screen.getByLabelText('Name:');
expect(nameInputElement).toBeInTheDocument();
`getByPlaceholderText`
Ta poizvedba najde element po njegovem nadomestnem besedilu (placeholder).
<input type="text" placeholder="Enter your email" />
const emailInputElement = screen.getByPlaceholderText('Enter your email');
expect(emailInputElement).toBeInTheDocument();
`getByAltText`
Ta poizvedba najde slikovni element po njegovem alt besedilu. Pomembno je, da za vse slike zagotovite smiselno alt besedilo, da zagotovite dostopnost.
<img src="logo.png" alt="Company Logo" />
const logoImageElement = screen.getByAltText('Company Logo');
expect(logoImageElement).toBeInTheDocument();
`getByTitle`
Ta poizvedba najde element po njegovem atributu `title`.
<span title="Close">X</span>
const closeElement = screen.getByTitle('Close');
expect(closeElement).toBeInTheDocument();
`getByDisplayValue`
Ta poizvedba najde element po njegovi prikazani vrednosti. To je uporabno za testiranje vnosnih polj obrazcev z vnaprej izpolnjenimi vrednostmi.
<input type="text" value="Initial Value" />
const inputElement = screen.getByDisplayValue('Initial Value');
expect(inputElement).toBeInTheDocument();
Poizvedbe `getAllBy*`
Poleg poizvedb `getBy*` RTL ponuja tudi poizvedbe `getAllBy*`, ki vrnejo polje ustreznih elementov. Te so uporabne, ko želite potrditi, da je v DOM-u prisotnih več elementov z enakimi značilnostmi.
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
const listItems = screen.getAllByRole('listitem');
expect(listItems).toHaveLength(3);
Poizvedbe `queryBy*`
Poizvedbe `queryBy*` so podobne poizvedbam `getBy*`, vendar vrnejo `null`, če noben ustrezen element ni najden, namesto da bi sprožile napako. To je uporabno, ko želite potrditi, da element *ni* prisoten v DOM-u.
const missingElement = screen.queryByText('Non-existent text');
expect(missingElement).toBeNull();
Poizvedbe `findBy*`
Poizvedbe `findBy*` so asinhrone različice poizvedb `getBy*`. Vrnejo `Promise`, ki se razreši, ko je ustrezen element najden. Te so uporabne za testiranje asinhronih operacij, kot je pridobivanje podatkov iz API-ja.
// Simulacija asinhronega pridobivanja podatkov
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();
});
Simuliranje interakcij uporabnika
RTL ponuja API-ja `fireEvent` in `userEvent` za simuliranje interakcij uporabnika, kot so klikanje gumbov, tipkanje v vnosna polja in pošiljanje obrazcev.
`fireEvent`
`fireEvent` vam omogoča programsko sprožanje DOM dogodkov. Je API nižje ravni, ki vam daje natančen nadzor nad sproženimi dogodki.
<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` je API višje ravni, ki bolj realistično simulira interakcije uporabnika. Upravlja podrobnosti, kot so upravljanje fokusa in vrstni red dogodkov, kar naredi vaše teste bolj robustne in manj krhke.
<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!');
});
Testiranje asinhrone kode
Številne aplikacije React vključujejo asinhrone operacije, kot je pridobivanje podatkov iz API-ja. RTL ponuja več orodij za testiranje asinhrone kode.
`waitFor`
`waitFor` vam omogoča, da počakate, da pogoj postane resničen, preden postavite trditev. Uporabno je za testiranje asinhronih operacij, ki za dokončanje potrebujejo nekaj časa.
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();
});
Poizvedbe `findBy*`
Kot smo že omenili, so poizvedbe `findBy*` asinhrone in vrnejo `Promise`, ki se razreši, ko je ustrezen element najden. Te so uporabne za testiranje asinhronih operacij, ki povzročijo spremembe v DOM-u.
Testiranje Hookov
React Hooki so ponovno uporabljive funkcije, ki enkapsulirajo logiko s stanjem. RTL ponuja pripomoček `renderHook` iz `@testing-library/react-hooks` (ki je od različice v17 opuščen v korist `@testing-library/react` neposredno) za testiranje Hookov po meri v izolaciji.
// 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);
});
Pojasnilo:
- `renderHook`: Ta funkcija renderira Hook in vrne objekt, ki vsebuje rezultat Hooka.
- `act`: Ta funkcija se uporablja za ovijanje katere koli kode, ki povzroča posodobitve stanja. To zagotavlja, da lahko React pravilno združi in obdela posodobitve.
Napredne tehnike testiranja
Ko obvladate osnove RTL, lahko raziščete naprednejše tehnike testiranja za izboljšanje kakovosti in vzdržljivosti vaših testov.
Mockiranje modulov
Včasih boste morda morali mockirati zunanje module ali odvisnosti, da izolirate svoje komponente in nadzorujete njihovo obnašanje med testiranjem. Jest za ta namen ponuja zmogljiv API za mockiranje.
// 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);
});
Pojasnilo:
- `jest.mock('../api/dataService')`: Ta vrstica mockira modul `dataService`.
- `dataService.fetchData.mockResolvedValue({ message: 'Mocked data!' })`: Ta vrstica konfigurira mockirano funkcijo `fetchData`, da vrne `Promise`, ki se razreši z navedenimi podatki.
- `expect(dataService.fetchData).toHaveBeenCalledTimes(1)`: Ta vrstica trdi, da je bila mockirana funkcija `fetchData` klicana enkrat.
Context Providerji
Če je vaša komponenta odvisna od Context Providerja, boste med testiranjem morali svojo komponento oviti v ta provider. To zagotavlja, da ima komponenta dostop do vrednosti konteksta.
// 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();
});
Pojasnilo:
- `MyComponent` ovijemo v `ThemeProvider`, da med testiranjem zagotovimo potreben kontekst.
Testiranje z Routerjem
Pri testiranju komponent, ki uporabljajo React Router, boste morali zagotoviti mockiran kontekst Routerja. To lahko dosežete z uporabo komponente `MemoryRouter` iz `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');
});
Pojasnilo:
- `MyComponent` ovijemo v `MemoryRouter`, da zagotovimo mockiran kontekst Routerja.
- Potrdimo, da ima element povezave pravilen atribut `href`.
Najboljše prakse za pisanje učinkovitih testov
Tukaj je nekaj najboljših praks, ki jih je treba upoštevati pri pisanju testov z RTL:
- Osredotočite se na interakcije uporabnika: Pišite teste, ki simulirajo, kako uporabniki komunicirajo z vašo aplikacijo.
- Izogibajte se testiranju podrobnosti implementacije: Ne testirajte notranjega delovanja vaših komponent. Namesto tega se osredotočite na opazovano obnašanje.
- Pišite jasne in jedrnate teste: Poskrbite, da bodo vaši testi enostavni za razumevanje in vzdrževanje.
- Uporabljajte smiselna imena testov: Izberite imena testov, ki natančno opisujejo testirano obnašanje.
- Ohranite teste izolirane: Izogibajte se odvisnostim med testi. Vsak test mora biti neodvisen in samostojen.
- Testirajte robne primere: Ne testirajte samo srečne poti. Poskrbite, da boste testirali tudi robne primere in pogoje napak.
- Pišite teste, preden kodirate: Razmislite o uporabi testno vodenega razvoja (TDD) za pisanje testov, preden napišete svojo kodo.
- Sledite vzorcu "AAA": Pripravi, Ukrepaj, Preveri (Arrange, Act, Assert). Ta vzorec pomaga strukturirati vaše teste in jih naredi bolj berljive.
- Ohranjajte hitrost testov: Počasni testi lahko odvrnejo razvijalce od pogostega zaganjanja. Optimizirajte hitrost testov z mockiranjem omrežnih zahtevkov in zmanjšanjem količine manipulacije DOM-a.
- Uporabljajte opisna sporočila o napakah: Ko trditve ne uspejo, morajo sporočila o napakah zagotoviti dovolj informacij za hitro prepoznavanje vzroka napake.
Zaključek
React Testing Library je zmogljivo orodje za pisanje učinkovitih, vzdržljivih in uporabniško usmerjenih testov za vaše aplikacije React. Z upoštevanjem načel in tehnik, opisanih v tem vodniku, lahko gradite robustne in zanesljive aplikacije, ki ustrezajo potrebam vaših uporabnikov. Ne pozabite se osredotočiti na testiranje z vidika uporabnika, izogibajte se testiranju podrobnosti implementacije in pišite jasne ter jedrnate teste. S sprejetjem RTL in upoštevanjem najboljših praks lahko bistveno izboljšate kakovost in vzdržljivost svojih projektov React, ne glede na vašo lokacijo ali specifične zahteve vaše globalne publike.