Sajátítsa el a React Testing Library (RTL) használatát ezzel a teljes körű útmutatóval. Tanulja meg, hogyan írjon hatékony, karbantartható és felhasználó-központú teszteket React alkalmazásaihoz, a legjobb gyakorlatokra és valós példákra összpontosítva.
React Testing Library: Átfogó útmutató
A mai rohanó webfejlesztési világban a React alkalmazások minőségének és megbízhatóságának biztosítása kiemelkedően fontos. A React Testing Library (RTL) népszerű és hatékony megoldássá vált a felhasználói szemszögre fókuszáló tesztek írására. Ez az útmutató teljes áttekintést nyújt az RTL-ről, az alapvető koncepcióktól a haladó technikákig, lehetővé téve, hogy robusztus és karbantartható React alkalmazásokat építsen.
Miért válassza a React Testing Library-t?
A hagyományos tesztelési megközelítések gyakran implementációs részletekre támaszkodnak, ami a teszteket törékennyé teszi, és kisebb kódváltoztatások esetén is hajlamosak meghibásodni. Ezzel szemben az RTL arra ösztönöz, hogy a komponenseket úgy tesztelje, ahogyan egy felhasználó interakcióba lépne velük, arra összpontosítva, amit a felhasználó lát és tapasztal. Ez a megközelítés számos kulcsfontosságú előnnyel jár:
- Felhasználó-központú tesztelés: Az RTL olyan tesztek írását támogatja, amelyek a felhasználó szemszögét tükrözik, biztosítva, hogy az alkalmazás a végfelhasználó szemszögéből elvárt módon működjön.
- Csökkentett teszttörékenység: Az implementációs részletek tesztelésének elkerülésével az RTL tesztek kevésbé valószínű, hogy megszakadnak a kód refaktorálása során, ami karbantarthatóbb és robusztusabb teszteket eredményez.
- Jobb kódtervezés: Az RTL arra ösztönöz, hogy hozzáférhető és könnyen használható komponenseket írjon, ami jobb általános kódtervezéshez vezet.
- Fókuszban a hozzáférhetőség: Az RTL megkönnyíti a komponensek hozzáférhetőségének tesztelését, biztosítva, hogy az alkalmazás mindenki számára használható legyen.
- Egyszerűsített tesztelési folyamat: Az RTL egyszerű és intuitív API-t biztosít, megkönnyítve a tesztek írását és karbantartását.
A tesztelési környezet beállítása
Mielőtt elkezdhetné használni az RTL-t, be kell állítania a tesztelési környezetet. Ez általában a szükséges függőségek telepítését és a tesztelési keretrendszer konfigurálását jelenti.
Előfeltételek
- Node.js és npm (vagy yarn): Győződjön meg róla, hogy a Node.js és az npm (vagy yarn) telepítve van a rendszerén. Letöltheti őket a hivatalos Node.js webhelyről.
- React projekt: Rendelkeznie kell egy meglévő React projekttel, vagy létre kell hoznia egy újat a Create React App vagy egy hasonló eszköz segítségével.
Telepítés
Telepítse a következő csomagokat az npm vagy a yarn használatával:
npm install --save-dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Vagy, a yarn használatával:
yarn add --dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Csomagok magyarázata:
- @testing-library/react: A React komponensek tesztelésének alapvető könyvtára.
- @testing-library/jest-dom: Egyéni Jest matchereket biztosít a DOM csomópontokkal kapcsolatos állításokhoz.
- Jest: Egy népszerű JavaScript tesztelési keretrendszer.
- babel-jest: Egy Jest transzformátor, amely a Babelt használja a kód lefordításához.
- @babel/preset-env: Egy Babel preset, amely meghatározza a célkörnyezetek támogatásához szükséges Babel bővítményeket és beállításokat.
- @babel/preset-react: Egy Babel preset a Reacthez.
Konfiguráció
Hozzon létre egy `babel.config.js` fájlt a projekt gyökerében a következő tartalommal:
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};
Frissítse a `package.json` fájlját, hogy tartalmazzon egy teszt parancsot:
{
"scripts": {
"test": "jest"
}
}
Hozzon létre egy `jest.config.js` fájlt a projekt gyökerében a Jest konfigurálásához. Egy minimális konfiguráció így nézhet ki:
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
};
Hozzon létre egy `src/setupTests.js` fájlt a következő tartalommal. Ez biztosítja, hogy a Jest DOM matcherek minden tesztben elérhetőek legyenek:
import '@testing-library/jest-dom/extend-expect';
Az első teszt megírása
Kezdjünk egy egyszerű példával. Tegyük fel, hogy van egy React komponense, amely egy üdvözlő üzenetet jelenít meg:
// src/components/Greeting.js
import React from 'react';
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
export default Greeting;
Most írjunk egy tesztet ehhez a komponenshez:
// 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();
});
Magyarázat:
- `render`: Ez a függvény rendereli a komponenst a DOM-ba.
- `screen`: Ez az objektum metódusokat biztosít a DOM lekérdezéséhez.
- `getByText`: Ez a metódus a szöveges tartalma alapján talál meg egy elemet. Az `/i` jelző a keresést kis- és nagybetű érzéketlenné teszi.
- `expect`: Ez a függvény a komponens viselkedésével kapcsolatos állítások megfogalmazására szolgál.
- `toBeInTheDocument`: Ez a matcher azt állítja, hogy az elem jelen van a DOM-ban.
A teszt futtatásához hajtsa végre a következő parancsot a terminálban:
npm test
Ha minden megfelelően van konfigurálva, a tesztnek sikeresnek kell lennie.
Gyakori RTL lekérdezések
Az RTL különféle lekérdezési metódusokat kínál az elemek DOM-ban való megtalálására. Ezek a lekérdezések úgy lettek kialakítva, hogy utánozzák, ahogyan a felhasználók interakcióba lépnek az alkalmazással.
`getByRole`
Ez a lekérdezés egy elemet az ARIA szerepe alapján talál meg. Jó gyakorlat a `getByRole` használata, amikor csak lehetséges, mivel ez elősegíti a hozzáférhetőséget és biztosítja, hogy a tesztek ellenálljanak a mögöttes DOM-struktúra változásainak.
<button role="button">Click me</button>
const buttonElement = screen.getByRole('button');
expect(buttonElement).toBeInTheDocument();
`getByLabelText`
Ez a lekérdezés egy elemet a hozzá társított címke szövege alapján talál meg. Hasznos űrlap elemek teszteléséhez.
<label htmlFor="name">Name:</label>
<input type="text" id="name" />
const nameInputElement = screen.getByLabelText('Name:');
expect(nameInputElement).toBeInTheDocument();
`getByPlaceholderText`
Ez a lekérdezés egy elemet a helyőrző szövege alapján talál meg.
<input type="text" placeholder="Enter your email" />
const emailInputElement = screen.getByPlaceholderText('Enter your email');
expect(emailInputElement).toBeInTheDocument();
`getByAltText`
Ez a lekérdezés egy kép elemet az alt szövege alapján talál meg. Fontos, hogy minden képhez értelmes alt szöveget biztosítsunk a hozzáférhetőség érdekében.
<img src="logo.png" alt="Company Logo" />
const logoImageElement = screen.getByAltText('Company Logo');
expect(logoImageElement).toBeInTheDocument();
`getByTitle`
Ez a lekérdezés egy elemet a title attribútuma alapján talál meg.
<span title="Close">X</span>
const closeElement = screen.getByTitle('Close');
expect(closeElement).toBeInTheDocument();
`getByDisplayValue`
Ez a lekérdezés egy elemet a megjelenített értéke alapján talál meg. Ez hasznos előre kitöltött értékekkel rendelkező űrlap beviteli mezők teszteléséhez.
<input type="text" value="Initial Value" />
const inputElement = screen.getByDisplayValue('Initial Value');
expect(inputElement).toBeInTheDocument();
`getAllBy*` lekérdezések
A `getBy*` lekérdezések mellett az RTL `getAllBy*` lekérdezéseket is biztosít, amelyek a megfelelő elemek tömbjét adják vissza. Ezek akkor hasznosak, ha azt kell állítani, hogy több, azonos jellemzőkkel rendelkező elem van jelen a DOM-ban.
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
const listItems = screen.getAllByRole('listitem');
expect(listItems).toHaveLength(3);
`queryBy*` lekérdezések
A `queryBy*` lekérdezések hasonlóak a `getBy*` lekérdezésekhez, de `null`-t adnak vissza, ha nem találnak megfelelő elemet, ahelyett, hogy hibát dobnának. Ez akkor hasznos, ha azt akarja állítani, hogy egy elem *nincs* jelen a DOM-ban.
const missingElement = screen.queryByText('Non-existent text');
expect(missingElement).toBeNull();
`findBy*` lekérdezések
A `findBy*` lekérdezések a `getBy*` lekérdezések aszinkron verziói. Egy Promise-t adnak vissza, amely akkor oldódik fel, amikor a megfelelő elem megtalálható. Ezek hasznosak aszinkron műveletek, például API-ból történő adatlehívás teszteléséhez.
// Aszinkron adatlekérés szimulálása
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();
});
Felhasználói interakciók szimulálása
Az RTL a `fireEvent` és `userEvent` API-kat biztosítja a felhasználói interakciók szimulálására, mint például gombok kattintása, beviteli mezőkbe való gépelés és űrlapok beküldése.
`fireEvent`
A `fireEvent` lehetővé teszi a DOM események programozott kiváltását. Ez egy alacsonyabb szintű API, amely finomabb vezérlést biztosít a kiváltott események felett.
<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`
A `userEvent` egy magasabb szintű API, amely valósághűbben szimulálja a felhasználói interakciókat. Kezeli az olyan részleteket, mint a fókuszkezelés és az eseménysorrend, így a tesztek robusztusabbak és kevésbé törékenyek lesznek.
<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!');
});
Aszinkron kód tesztelése
Sok React alkalmazás tartalmaz aszinkron műveleteket, például adatlehívást egy API-ból. Az RTL számos eszközt biztosít az aszinkron kód teszteléséhez.
`waitFor`
A `waitFor` lehetővé teszi, hogy megvárja egy feltétel teljesülését, mielőtt állítást tenne. Hasznos olyan aszinkron műveletek teszteléséhez, amelyek végrehajtása időt vesz igénybe.
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*` lekérdezések
Ahogy korábban említettük, a `findBy*` lekérdezések aszinkronok és egy Promise-t adnak vissza, amely akkor oldódik fel, amikor a megfelelő elem megtalálható. Ezek hasznosak olyan aszinkron műveletek teszteléséhez, amelyek a DOM változását eredményezik.
Hook-ok tesztelése
A React Hook-ok újrahasználható függvények, amelyek állapotlogikát foglalnak magukban. Az RTL a `renderHook` segédprogramot biztosítja a `@testing-library/react-hooks`-ból (amely a v17-től kezdve elavult, és a `@testing-library/react`-ben található meg) az egyéni Hook-ok izolált tesztelésére.
// 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);
});
Magyarázat:
- `renderHook`: Ez a függvény rendereli a Hook-ot, és visszaad egy objektumot, amely a Hook eredményét tartalmazza.
- `act`: Ez a függvény arra szolgál, hogy becsomagoljon minden olyan kódot, amely állapotfrissítést okoz. Ez biztosítja, hogy a React megfelelően tudja kötegelni és feldolgozni a frissítéseket.
Haladó tesztelési technikák
Miután elsajátította az RTL alapjait, felfedezhet haladóbb tesztelési technikákat a tesztjei minőségének és karbantarthatóságának javítása érdekében.
Modulok mockolása
Néha szükség lehet külső modulok vagy függőségek mockolására a komponensek izolálása és viselkedésük irányítása érdekében a tesztelés során. A Jest erre a célra egy erőteljes mockolási API-t biztosít.
// 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);
});
Magyarázat:
- `jest.mock('../api/dataService')`: Ez a sor mockolja a `dataService` modult.
- `dataService.fetchData.mockResolvedValue({ message: 'Mocked data!' })`: Ez a sor beállítja a mockolt `fetchData` függvényt, hogy egy olyan Promise-t adjon vissza, amely a megadott adatokkal oldódik fel.
- `expect(dataService.fetchData).toHaveBeenCalledTimes(1)`: Ez a sor azt állítja, hogy a mockolt `fetchData` függvényt egyszer hívták meg.
Context Providerek
Ha a komponense egy Context Providerre támaszkodik, akkor a tesztelés során a komponenst be kell csomagolnia a providerbe. Ez biztosítja, hogy a komponens hozzáférjen a kontextus értékeihez.
// 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();
});
Magyarázat:
- A `MyComponent`-t a `ThemeProvider`-be csomagoljuk, hogy a tesztelés során biztosítsuk a szükséges kontextust.
Tesztelés Routerrel
Amikor React Routert használó komponenseket tesztel, mock Router kontextust kell biztosítania. Ezt a `react-router-dom` `MemoryRouter` komponensével érheti el.
// 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');
});
Magyarázat:
- A `MyComponent`-t a `MemoryRouter`-be csomagoljuk, hogy mock Router kontextust biztosítsunk.
- Azt állítjuk, hogy a link elemnek helyes `href` attribútuma van.
Legjobb gyakorlatok a hatékony tesztek írásához
Íme néhány legjobb gyakorlat, amelyet követhet az RTL-lel való tesztírás során:
- Fókuszáljon a felhasználói interakciókra: Írjon olyan teszteket, amelyek szimulálják, hogyan lépnek kapcsolatba a felhasználók az alkalmazással.
- Kerülje az implementációs részletek tesztelését: Ne tesztelje a komponensek belső működését. Ehelyett összpontosítson a megfigyelhető viselkedésre.
- Írjon világos és tömör teszteket: Tegye a tesztjeit könnyen érthetővé és karbantarthatóvá.
- Használjon értelmes tesztneveket: Válasszon olyan tesztneveket, amelyek pontosan leírják a tesztelt viselkedést.
- Tartsa a teszteket izoláltan: Kerülje a tesztek közötti függőségeket. Minden tesztnek függetlennek és önállónak kell lennie.
- Tesztelje a szélsőséges eseteket: Ne csak a "happy path"-t tesztelje. Győződjön meg róla, hogy a szélsőséges eseteket és a hibaállapotokat is teszteli.
- Írjon teszteket kódolás előtt: Fontolja meg a tesztvezérelt fejlesztés (TDD) alkalmazását, hogy a kód írása előtt írja meg a teszteket.
- Kövesse az „AAA” mintát: Arrange (Előkészítés), Act (Cselekvés), Assert (Állítás). Ez a minta segít strukturálni a teszteket és olvashatóbbá tenni őket.
- Tartsa a teszteket gyorsan: A lassú tesztek elriaszthatják a fejlesztőket attól, hogy gyakran futtassák őket. Optimalizálja a teszteket a sebesség érdekében a hálózati kérések mockolásával és a DOM-manipuláció minimalizálásával.
- Használjon leíró hibaüzeneteket: Amikor az állítások meghiúsulnak, a hibaüzeneteknek elegendő információt kell nyújtaniuk a hiba okának gyors azonosításához.
Következtetés
A React Testing Library egy hatékony eszköz a React alkalmazások hatékony, karbantartható és felhasználó-központú tesztjeinek írásához. Az ebben az útmutatóban felvázolt elvek és technikák követésével robusztus és megbízható alkalmazásokat építhet, amelyek megfelelnek a felhasználók igényeinek. Ne feledje, hogy a felhasználó szemszögéből teszteljen, kerülje az implementációs részletek tesztelését, és írjon világos és tömör teszteket. Az RTL felkarolásával és a legjobb gyakorlatok alkalmazásával jelentősen javíthatja React projektjei minőségét és karbantarthatóságát, függetlenül a tartózkodási helyétől vagy a globális közönség specifikus követelményeitől.