Hallitse React Testing Library (RTL) tällä oppaalla. Opi kirjoittamaan tehokkaita, ylläpidettäviä ja käyttäjäkeskeisiä testejä React-sovelluksillesi.
React Testing Library: Kattava opas
Nykypäivän nopeatahtisessa verkkokehitysmaailmassa React-sovellusten laadun ja luotettavuuden varmistaminen on ensisijaisen tärkeää. React Testing Library (RTL) on noussut suosituksi ja tehokkaaksi ratkaisuksi käyttäjän näkökulmaan keskittyvien testien kirjoittamiseen. Tämä opas tarjoaa täydellisen yleiskatsauksen RTL:ään, kattaen kaiken peruskäsitteistä edistyneisiin tekniikoihin, ja antaa sinulle valmiudet rakentaa vankkoja ja ylläpidettäviä React-sovelluksia.
Miksi valita React Testing Library?
Perinteiset testausmenetelmät tukeutuvat usein toteutuksen yksityiskohtiin, mikä tekee testeistä hauraita ja alttiita rikkoutumaan pienissäkin koodimuutoksissa. RTL sen sijaan kannustaa sinua testaamaan komponenttejasi niin kuin käyttäjä olisi vuorovaikutuksessa niiden kanssa, keskittyen siihen, mitä käyttäjä näkee ja kokee. Tämä lähestymistapa tarjoaa useita keskeisiä etuja:
- Käyttäjäkeskeinen testaus: RTL edistää testien kirjoittamista, jotka heijastavat käyttäjän näkökulmaa, varmistaen, että sovelluksesi toimii odotetusti loppukäyttäjän kannalta.
- Vähemmän hauraat testit: Välttämällä toteutuksen yksityiskohtien testaamista RTL-testit rikkoutuvat epätodennäköisemmin, kun refaktoroit koodiasi, mikä johtaa ylläpidettävämpiin ja vankempiin testeihin.
- Parempi koodin suunnittelu: RTL kannustaa sinua kirjoittamaan komponentteja, jotka ovat saavutettavia ja helppokäyttöisiä, mikä johtaa parempaan yleiseen koodin suunnitteluun.
- Keskittyminen saavutettavuuteen: RTL helpottaa komponenttiesi saavutettavuuden testaamista, varmistaen, että sovelluksesi on kaikkien käytettävissä.
- Yksinkertaistettu testausprosessi: RTL tarjoaa yksinkertaisen ja intuitiivisen API:n, mikä helpottaa testien kirjoittamista ja ylläpitoa.
Testausympäristön pystyttäminen
Ennen kuin voit aloittaa RTL:n käytön, sinun on pystytettävä testausympäristösi. Tämä sisältää tyypillisesti tarvittavien riippuvuuksien asentamisen ja testauskehyksen konfiguroinnin.
Edellytykset
- Node.js ja npm (tai yarn): Varmista, että sinulla on Node.js ja npm (tai yarn) asennettuna järjestelmääsi. Voit ladata ne viralliselta Node.js-verkkosivustolta.
- React-projekti: Sinulla tulisi olla olemassa oleva React-projekti tai luo uusi käyttämällä Create React Appia tai vastaavaa työkalua.
Asennus
Asenna seuraavat paketit käyttämällä npm:ää tai yarnia:
npm install --save-dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Tai yarnilla:
yarn add --dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react
Pakettien selitykset:
- @testing-library/react: Ydinkirjasto React-komponenttien testaamiseen.
- @testing-library/jest-dom: Tarjoaa mukautettuja Jest-matchereita DOM-solmuja koskeviin väittämiin.
- Jest: Suosittu JavaScript-testauskehys.
- babel-jest: Jest-muuntaja, joka käyttää Babelia koodisi kääntämiseen.
- @babel/preset-env: Babel-esiasetus, joka määrittää Babel-lisäosat ja -esiasetukset, joita tarvitaan kohdeympäristöjen tukemiseen.
- @babel/preset-react: Babel-esiasetus Reactille.
Konfiguraatio
Luo `babel.config.js`-tiedosto projektisi juureen seuraavalla sisällöllä:
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};
Päivitä `package.json`-tiedostosi sisältämään testiskriptin:
{
"scripts": {
"test": "jest"
}
}
Luo `jest.config.js`-tiedosto projektisi juureen Jestin konfiguroimiseksi. Minimaalinen konfiguraatio voisi näyttää tältä:
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
};
Luo `src/setupTests.js`-tiedosto seuraavalla sisällöllä. Tämä varmistaa, että Jest DOM -matcherit ovat saatavilla kaikissa testeissäsi:
import '@testing-library/jest-dom/extend-expect';
Ensimmäisen testin kirjoittaminen
Aloitetaan yksinkertaisella esimerkillä. Oletetaan, että sinulla on React-komponentti, joka näyttää tervehdysviestin:
// src/components/Greeting.js
import React from 'react';
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
export default Greeting;
Kirjoitetaan nyt testi tälle komponentille:
// 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();
});
Selitys:
- `render`: Tämä funktio renderöi komponentin DOM:iin.
- `screen`: Tämä olio tarjoaa metodeja DOM-kyselyiden tekemiseen.
- `getByText`: Tämä metodi löytää elementin sen tekstisisällön perusteella. `/i`-lippu tekee hausta kirjainkoosta riippumattoman.
- `expect`: Tätä funktiota käytetään väittämien tekemiseen komponentin käyttäytymisestä.
- `toBeInTheDocument`: Tämä matcheri väittää, että elementti on läsnä DOM:ssa.
Ajaaksesi testin, suorita seuraava komento päätteessäsi:
npm test
Jos kaikki on konfiguroitu oikein, testin pitäisi mennä läpi.
Yleiset RTL-kyselyt
RTL tarjoaa useita kyselymetodeja elementtien löytämiseen DOM:sta. Nämä kyselyt on suunniteltu jäljittelemään, miten käyttäjät ovat vuorovaikutuksessa sovelluksesi kanssa.
`getByRole`
Tämä kysely löytää elementin sen ARIA-roolin perusteella. On hyvä käytäntö käyttää `getByRole`-kyselyä aina kun mahdollista, koska se edistää saavutettavuutta ja varmistaa, että testisi kestävät muutoksia taustalla olevassa DOM-rakenteessa.
<button role="button">Click me</button>
const buttonElement = screen.getByRole('button');
expect(buttonElement).toBeInTheDocument();
`getByLabelText`
Tämä kysely löytää elementin siihen liitetyn label-elementin tekstin perusteella. Se on hyödyllinen lomake-elementtien testaamisessa.
<label htmlFor="name">Name:</label>
<input type="text" id="name" />
const nameInputElement = screen.getByLabelText('Name:');
expect(nameInputElement).toBeInTheDocument();
`getByPlaceholderText`
Tämä kysely löytää elementin sen placeholder-tekstin perusteella.
<input type="text" placeholder="Enter your email" />
const emailInputElement = screen.getByPlaceholderText('Enter your email');
expect(emailInputElement).toBeInTheDocument();
`getByAltText`
Tämä kysely löytää kuvaelementin sen alt-tekstin perusteella. On tärkeää tarjota merkityksellinen alt-teksti kaikille kuville saavutettavuuden varmistamiseksi.
<img src="logo.png" alt="Company Logo" />
const logoImageElement = screen.getByAltText('Company Logo');
expect(logoImageElement).toBeInTheDocument();
`getByTitle`
Tämä kysely löytää elementin sen title-attribuutin perusteella.
<span title="Close">X</span>
const closeElement = screen.getByTitle('Close');
expect(closeElement).toBeInTheDocument();
`getByDisplayValue`
Tämä kysely löytää elementin sen näyttöarvon perusteella. Tämä on hyödyllistä testattaessa lomakkeen syöttökenttiä, joissa on ennalta täytettyjä arvoja.
<input type="text" value="Initial Value" />
const inputElement = screen.getByDisplayValue('Initial Value');
expect(inputElement).toBeInTheDocument();
`getAllBy*`-kyselyt
`getBy*`-kyselyiden lisäksi RTL tarjoaa myös `getAllBy*`-kyselyitä, jotka palauttavat taulukon vastaavista elementeistä. Nämä ovat hyödyllisiä, kun haluat varmistaa, että useita samanlaisia elementtejä on läsnä DOM:ssa.
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
const listItems = screen.getAllByRole('listitem');
expect(listItems).toHaveLength(3);
`queryBy*`-kyselyt
`queryBy*`-kyselyt ovat samankaltaisia kuin `getBy*`-kyselyt, mutta ne palauttavat `null`, jos vastaavaa elementtiä ei löydy, sen sijaan että heittäisivät virheen. Tämä on hyödyllistä, kun haluat varmistaa, että elementtiä *ei* ole DOM:ssa.
const missingElement = screen.queryByText('Non-existent text');
expect(missingElement).toBeNull();
`findBy*`-kyselyt
`findBy*`-kyselyt ovat `getBy*`-kyselyiden asynkronisia versioita. Ne palauttavat Promisen, joka ratkeaa, kun vastaava elementti löytyy. Nämä ovat hyödyllisiä asynkronisten operaatioiden testaamisessa, kuten datan noutamisessa API:sta.
// 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();
});
Käyttäjäinteraktioiden simulointi
RTL tarjoaa `fireEvent`- ja `userEvent`-API:t käyttäjäinteraktioiden simulointiin, kuten painikkeiden napsauttamiseen, syöttökenttiin kirjoittamiseen ja lomakkeiden lähettämiseen.
`fireEvent`
`fireEvent` mahdollistaa DOM-tapahtumien ohjelmallisen laukaisemisen. Se on matalamman tason API, joka antaa sinulle hienojakoista hallintaa laukaistavista tapahtumista.
<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` on korkeamman tason API, joka simuloi käyttäjäinteraktioita realistisemmin. Se käsittelee yksityiskohtia, kuten fokuksen hallintaa ja tapahtumien järjestystä, tehden testeistäsi vankempia ja vähemmän hauraita.
<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!');
});
Asynkronisen koodin testaaminen
Monet React-sovellukset sisältävät asynkronisia operaatioita, kuten datan noutamista API:sta. RTL tarjoaa useita työkaluja asynkronisen koodin testaamiseen.
`waitFor`
`waitFor` antaa sinun odottaa, kunnes tietty ehto täyttyy, ennen kuin teet väittämän. Se on hyödyllinen testattaessa asynkronisia operaatioita, joiden suorittaminen kestää jonkin aikaa.
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*`-kyselyt
Kuten aiemmin mainittiin, `findBy*`-kyselyt ovat asynkronisia ja palauttavat Promisen, joka ratkeaa, kun vastaava elementti löytyy. Nämä ovat hyödyllisiä testattaessa asynkronisia operaatioita, jotka johtavat muutoksiin DOM:ssa.
Hookien testaaminen
React Hookit ovat uudelleenkäytettäviä funktioita, jotka kapseloivat tilallista logiikkaa. RTL tarjoaa `renderHook`-apuohjelman paketista `@testing-library/react-hooks` (joka on vanhentunut ja korvattu suoraan `@testing-library/react`:llä versiosta 17 lähtien) mukautettujen Hookien testaamiseen erikseen.
// 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);
});
Selitys:
- `renderHook`: Tämä funktio renderöi Hookin ja palauttaa olion, joka sisältää Hookin tuloksen.
- `act`: Tätä funktiota käytetään kietomaan kaikki koodi, joka aiheuttaa tilapäivityksiä. Tämä varmistaa, että React voi käsitellä ja ryhmitellä päivitykset oikein.
Edistyneet testaustekniikat
Kun olet oppinut RTL:n perusteet, voit tutustua edistyneempiin testaustekniikoihin parantaaksesi testiesi laatua ja ylläpidettävyyttä.
Moduulien mockaaminen
Joskus sinun voi olla tarpeen mockata ulkoisia moduuleja tai riippuvuuksia eristääksesi komponenttisi ja hallitaksesi niiden käyttäytymistä testauksen aikana. Jest tarjoaa tähän tarkoitukseen tehokkaan mockaus-API:n.
// 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);
});
Selitys:
- `jest.mock('../api/dataService')`: Tämä rivi mockaa `dataService`-moduulin.
- `dataService.fetchData.mockResolvedValue({ message: 'Mocked data!' })`: Tämä rivi konfiguroi mockatun `fetchData`-funktion palauttamaan Promisen, joka ratkeaa määritetyllä datalla.
- `expect(dataService.fetchData).toHaveBeenCalledTimes(1)`: Tämä rivi väittää, että mockattua `fetchData`-funktiota kutsuttiin kerran.
Context Providerit
Jos komponenttisi tukeutuu Context Provideriin, sinun on käärittävä komponenttisi provideriin testauksen aikana. Tämä varmistaa, että komponentilla on pääsy kontekstin arvoihin.
// 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();
});
Selitys:
- Käärimme `MyComponent`-komponentin `ThemeProvideriin` tarjotaksemme tarvittavan kontekstin testauksen aikana.
Testaaminen Routerin kanssa
Kun testaat komponentteja, jotka käyttävät React Routeria, sinun on tarjottava mockattu Router-konteksti. Voit saavuttaa tämän käyttämällä `MemoryRouter`-komponenttia `react-router-dom`-paketista.
// 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');
});
Selitys:
- Käärimme `MyComponent`-komponentin `MemoryRouteriin` tarjotaksemme mockatun Router-kontekstin.
- Varmistamme, että linkkielementillä on oikea `href`-attribuutti.
Parhaat käytännöt tehokkaiden testien kirjoittamiseen
Tässä on joitakin parhaita käytäntöjä, joita noudattaa kirjoittaessasi testejä RTL:llä:
- Keskity käyttäjäinteraktioihin: Kirjoita testejä, jotka simuloivat, kuinka käyttäjät ovat vuorovaikutuksessa sovelluksesi kanssa.
- Vältä toteutuksen yksityiskohtien testaamista: Älä testaa komponenttiesi sisäistä toimintaa. Keskity sen sijaan havaittavaan käyttäytymiseen.
- Kirjoita selkeitä ja ytimekkäitä testejä: Tee testeistäsi helposti ymmärrettäviä ja ylläpidettäviä.
- Käytä kuvaavia testien nimiä: Valitse testien nimet, jotka kuvaavat tarkasti testattavaa toimintaa.
- Pidä testit eristettyinä: Vältä riippuvuuksia testien välillä. Jokaisen testin tulisi olla itsenäinen ja erillinen.
- Testaa reunatapaukset: Älä testaa vain onnistumisen polkua. Varmista, että testaat myös reunatapaukset ja virhetilanteet.
- Kirjoita testit ennen koodia: Harkitse testivetoista kehitystä (TDD) ja kirjoita testit ennen kuin kirjoitat koodisi.
- Noudata "AAA"-mallia: Arrange, Act, Assert (Järjestä, Toimi, Varmista). Tämä malli auttaa jäsentämään testejäsi ja tekemään niistä luettavampia.
- Pidä testit nopeina: Hitaat testit voivat lannistaa kehittäjiä ajamasta niitä usein. Optimoi testiesi nopeus mockaamalla verkkopyyntöjä ja minimoimalla DOM-manipulaation määrä.
- Käytä kuvaavia virheilmoituksia: Kun väittämät epäonnistuvat, virheilmoitusten tulisi antaa riittävästi tietoa epäonnistumisen syyn nopeaan tunnistamiseen.
Yhteenveto
React Testing Library on tehokas työkalu tehokkaiden, ylläpidettävien ja käyttäjäkeskeisten testien kirjoittamiseen React-sovelluksillesi. Noudattamalla tässä oppaassa esitettyjä periaatteita ja tekniikoita voit rakentaa vankkoja ja luotettavia sovelluksia, jotka vastaavat käyttäjiesi tarpeita. Muista keskittyä testaamiseen käyttäjän näkökulmasta, välttää toteutuksen yksityiskohtien testaamista ja kirjoittaa selkeitä ja ytimekkäitä testejä. Ottamalla RTL:n käyttöön ja omaksumalla parhaat käytännöt voit merkittävästi parantaa React-projektiesi laatua ja ylläpidettävyyttä, riippumatta sijainnistasi tai globaalin yleisösi erityisvaatimuksista.