Suomi

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:

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

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:

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:

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:

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:

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:

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:

Parhaat käytännöt tehokkaiden testien kirjoittamiseen

Tässä on joitakin parhaita käytäntöjä, joita noudattaa kirjoittaessasi testejä RTL:llä:

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.