Lietuvių

Įsisavinkite „React Testing Library“ (RTL) su šiuo išsamiu vadovu. Išmokite rašyti efektyvius, palaikomus ir į vartotoją orientuotus testus savo „React“ programoms, sutelkiant dėmesį į geriausias praktikas ir realius pavyzdžius.

React Testing Library: išsamus vadovas

Šiuolaikinėje sparčiai kintančioje interneto svetainių kūrimo aplinkoje itin svarbu užtikrinti „React“ programų kokybę ir patikimumą. „React Testing Library“ (RTL) tapo populiariu ir efektyviu sprendimu rašyti testus, kurie orientuoti į vartotojo perspektyvą. Šis vadovas pateikia išsamią RTL apžvalgą, apimančią viską nuo pagrindinių koncepcijų iki pažangių technikų, ir suteikia jums galimybę kurti patikimas ir lengvai palaikomas „React“ programas.

Kodėl verta rinktis „React Testing Library“?

Tradiciniai testavimo metodai dažnai remiasi įgyvendinimo detalėmis, todėl testai tampa trapūs ir linkę lūžti po menkiausių kodo pakeitimų. Kita vertus, RTL skatina testuoti komponentus taip, kaip su jais sąveikautų vartotojas, sutelkiant dėmesį į tai, ką vartotojas mato ir patiria. Šis požiūris suteikia keletą svarbių pranašumų:

Testavimo aplinkos paruošimas

Prieš pradedant naudoti RTL, turite paruošti savo testavimo aplinką. Paprastai tai apima būtinų priklausomybių diegimą ir testavimo karkaso konfigūravimą.

Būtinosios sąlygos

Diegimas

Įdiekite šiuos paketus naudodami npm arba yarn:

npm install --save-dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react

Arba, naudojant yarn:

yarn add --dev @testing-library/react @testing-library/jest-dom jest babel-jest @babel/preset-env @babel/preset-react

Paketų paaiškinimas:

Konfigūracija

Projekto šakninėje direktorijoje sukurkite `babel.config.js` failą su šiuo turiniu:

module.exports = {
  presets: ['@babel/preset-env', '@babel/preset-react'],
};

Atnaujinkite savo `package.json` failą, įtraukdami testavimo scenarijų:

{
  "scripts": {
    "test": "jest"
  }
}

Projekto šakninėje direktorijoje sukurkite `jest.config.js` failą, kad sukonfigūruotumėte „Jest“. Minimali konfigūracija galėtų atrodyti taip:

module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['/src/setupTests.js'],
};

Sukurkite `src/setupTests.js` failą su šiuo turiniu. Tai užtikrins, kad „Jest DOM“ derintojai (matchers) bus prieinami visuose jūsų testuose:

import '@testing-library/jest-dom/extend-expect';

Pirmojo testo rašymas

Pradėkime nuo paprasto pavyzdžio. Tarkime, turite „React“ komponentą, kuris rodo pasveikinimo pranešimą:

// src/components/Greeting.js
import React from 'react';

function Greeting({ name }) {
  return <h1>Sveiki, {name}!</h1>;
}

export default Greeting;

Dabar parašykime šio komponento testą:

// src/components/Greeting.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import Greeting from './Greeting';

test('atvaizduoja pasveikinimo pranešimą', () => {
  render(<Greeting name="Pasauli" />);
  const greetingElement = screen.getByText(/Sveiki, Pasauli!/i);
  expect(greetingElement).toBeInTheDocument();
});

Paaiškinimas:

Norėdami paleisti testą, terminale įvykdykite šią komandą:

npm test

Jei viskas sukonfigūruota teisingai, testas turėtų būti sėkmingas.

Įprastos RTL užklausos

RTL pateikia įvairius užklausų metodus elementams DOM'e rasti. Šios užklausos sukurtos imituoti, kaip vartotojai sąveikauja su jūsų programa.

`getByRole`

Ši užklausa suranda elementą pagal jo ARIA rolę. Gera praktika yra naudoti `getByRole` kai tik įmanoma, nes tai skatina prieinamumą ir užtikrina, kad jūsų testai būtų atsparūs pokyčiams pagrindinėje DOM struktūroje.

<button role="button">Spausk mane</button>
const buttonElement = screen.getByRole('button');
expect(buttonElement).toBeInTheDocument();

`getByLabelText`

Ši užklausa suranda elementą pagal susijusios etiketės (label) tekstą. Tai naudinga testuojant formos elementus.

<label htmlFor="name">Vardas:</label>
<input type="text" id="name" />
const nameInputElement = screen.getByLabelText('Vardas:');
expect(nameInputElement).toBeInTheDocument();

`getByPlaceholderText`

Ši užklausa suranda elementą pagal jo rezervuotos vietos (placeholder) tekstą.

<input type="text" placeholder="Įveskite savo el. paštą" />
const emailInputElement = screen.getByPlaceholderText('Įveskite savo el. paštą');
expect(emailInputElement).toBeInTheDocument();

`getByAltText`

Ši užklausa suranda paveikslėlio elementą pagal jo alternatyvų (alt) tekstą. Svarbu pateikti prasmingą alternatyvų tekstą visiems paveikslėliams, siekiant užtikrinti prieinamumą.

<img src="logo.png" alt="Įmonės logotipas" />
const logoImageElement = screen.getByAltText('Įmonės logotipas');
expect(logoImageElement).toBeInTheDocument();

`getByTitle`

Ši užklausa suranda elementą pagal jo pavadinimo (title) atributą.

<span title="Uždaryti">X</span>
const closeElement = screen.getByTitle('Uždaryti');
expect(closeElement).toBeInTheDocument();

`getByDisplayValue`

Ši užklausa suranda elementą pagal jo rodomą reikšmę. Tai naudinga testuojant formos įvesties laukus su iš anksto užpildytomis reikšmėmis.

<input type="text" value="Pradinė reikšmė" />
const inputElement = screen.getByDisplayValue('Pradinė reikšmė');
expect(inputElement).toBeInTheDocument();

`getAllBy*` užklausos

Be `getBy*` užklausų, RTL taip pat teikia `getAllBy*` užklausas, kurios grąžina atitinkančių elementų masyvą. Tai naudinga, kai reikia patvirtinti, kad DOM'e yra keli elementai su tomis pačiomis savybėmis.

<li>Punktas 1</li>
<li>Punktas 2</li>
<li>Punktas 3</li>
const listItems = screen.getAllByRole('listitem');
expect(listItems).toHaveLength(3);

`queryBy*` užklausos

`queryBy*` užklausos yra panašios į `getBy*` užklausas, tačiau jos grąžina `null`, jei nerandamas atitinkamas elementas, užuot išmetus klaidą. Tai naudinga, kai norite patvirtinti, kad elemento *nėra* DOM'e.

const missingElement = screen.queryByText('Neegzistuojantis tekstas');
expect(missingElement).toBeNull();

`findBy*` užklausos

`findBy*` užklausos yra asinchroninės `getBy*` užklausų versijos. Jos grąžina `Promise`, kuris išsipildo, kai randamas atitinkamas elementas. Jos naudingos testuojant asinchronines operacijas, pvz., duomenų gavimą iš API.

// Imituojamas asinchroninis duomenų gavimas
const fetchData = () => new Promise(resolve => {
  setTimeout(() => resolve('Duomenys įkelti!'), 1000);
});

function MyComponent() {
  const [data, setData] = React.useState(null);

  React.useEffect(() => {
    fetchData().then(setData);
  }, []);

  return <div>{data}</div>;
}
test('įkelia duomenis asinchroniškai', async () => {
  render(<MyComponent />);
  const dataElement = await screen.findByText('Duomenys įkelti!');
  expect(dataElement).toBeInTheDocument();
});

Vartotojo sąveikų imitavimas

RTL pateikia `fireEvent` ir `userEvent` API, skirtas imituoti vartotojo sąveikas, pvz., mygtukų paspaudimus, teksto įvedimą į laukelius ir formų pateikimą.

`fireEvent`

`fireEvent` leidžia programiškai sukelti DOM įvykius. Tai žemesnio lygio API, suteikianti smulkiagrūdę kontrolę pār sukeliamus įvykius.

<button onClick={() => alert('Mygtukas paspaustas!')}>Spausk mane</button>
import { fireEvent } from '@testing-library/react';

test('imituoja mygtuko paspaudimą', () => {
  const alertMock = jest.spyOn(window, 'alert').mockImplementation(() => {});
  render(<button onClick={() => alert('Mygtukas paspaustas!')}>Spausk mane</button>);
  const buttonElement = screen.getByRole('button');
  fireEvent.click(buttonElement);
  expect(alertMock).toHaveBeenCalledTimes(1);
  alertMock.mockRestore();
});

`userEvent`

`userEvent` yra aukštesnio lygio API, kuri imituoja vartotojo sąveikas realistiškiau. Ji tvarko tokias detales kaip fokusavimo valdymas ir įvykių eiliškumas, todėl jūsų testai tampa patikimesni ir mažiau trapūs.

<input type="text" onChange={e => console.log(e.target.value)} />
import userEvent from '@testing-library/user-event';

test('imituoja teksto įvedimą į laukelį', () => {
  const inputElement = screen.getByRole('textbox');
  userEvent.type(inputElement, 'Sveiki, pasauli!');
  expect(inputElement).toHaveValue('Sveiki, pasauli!');
});

Asinchroninio kodo testavimas

Daugelis „React“ programų apima asinchronines operacijas, tokias kaip duomenų gavimas iš API. RTL pateikia keletą įrankių asinchroniniam kodui testuoti.

`waitFor`

`waitFor` leidžia laukti, kol tam tikra sąlyga taps teisinga, prieš atliekant tvirtinimą. Tai naudinga testuojant asinchronines operacijas, kurioms įvykdyti reikia šiek tiek laiko.

function MyComponent() {
  const [data, setData] = React.useState(null);

  React.useEffect(() => {
    setTimeout(() => {
      setData('Duomenys įkelti!');
    }, 1000);
  }, []);

  return <div>{data}</div>;
}
import { waitFor } from '@testing-library/react';

test('laukia, kol duomenys bus įkelti', async () => {
  render(<MyComponent />);
  await waitFor(() => screen.getByText('Duomenys įkelti!'));
  const dataElement = screen.getByText('Duomenys įkelti!');
  expect(dataElement).toBeInTheDocument();
});

`findBy*` užklausos

Kaip minėta anksčiau, `findBy*` užklausos yra asinchroninės ir grąžina `Promise`, kuris išsipildo, kai randamas atitinkamas elementas. Jos naudingos testuojant asinchronines operacijas, kurios sukelia pakeitimus DOM.

„Hooks“ testavimas

„React Hooks“ yra daugkartinio naudojimo funkcijos, kurios apgaubia būsenos logiką. RTL pateikia `renderHook` įrankį iš `@testing-library/react-hooks` (kuris nebenaudojamas nuo v17, pirmenybę teikiant `@testing-library/react` tiesiogiai), skirtą testuoti pasirinktinius „Hooks“ atskirai.

// 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('padidina skaitiklį', () => {
  const { result } = renderHook(() => useCounter());

  act(() => {
    result.current.increment();
  });

  expect(result.current.count).toBe(1);
});

Paaiškinimas:

Pažangios testavimo technikos

Įsisavinę RTL pagrindus, galite tyrinėti pažangesnes testavimo technikas, kad pagerintumėte savo testų kokybę ir palaikomumą.

Modulių imitavimas (mocking)

Kartais gali tekti imituoti išorinius modulius ar priklausomybes, kad išskirtumėte savo komponentus ir kontroliuotumėte jų elgseną testavimo metu. „Jest“ šiam tikslui suteikia galingą imitavimo API.

// 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('gauna duomenis iš API', async () => {
  dataService.fetchData.mockResolvedValue({ message: 'Imituoti duomenys!' });

  render(<MyComponent />);

  await waitFor(() => screen.getByText('Imituoti duomenys!'));

  expect(screen.getByText('Imituoti duomenys!')).toBeInTheDocument();
  expect(dataService.fetchData).toHaveBeenCalledTimes(1);
});

Paaiškinimas:

Konteksto teikėjai (Context Providers)

Jei jūsų komponentas priklauso nuo konteksto teikėjo (Context Provider), testavimo metu turėsite apgaubti savo komponentą teikėju. Tai užtikrina, kad komponentas turės prieigą prie konteksto reikšmių.

// 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>Dabartinė tema: {theme}</p>
      <button onClick={toggleTheme}>Keisti temą</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('perjungia temą', () => {
  render(
    <ThemeProvider>
      <MyComponent />
    </ThemeProvider>
  );

  const themeParagraph = screen.getByText(/Dabartinė tema: light/i);
  const toggleButton = screen.getByRole('button', { name: /Keisti temą/i });

  expect(themeParagraph).toBeInTheDocument();

  fireEvent.click(toggleButton);

  expect(screen.getByText(/Dabartinė tema: dark/i)).toBeInTheDocument();
});

Paaiškinimas:

Testavimas su „Router“

Testuojant komponentus, kurie naudoja „React Router“, reikės pateikti imituotą „Router“ kontekstą. Tai galite pasiekti naudodami `MemoryRouter` komponentą iš `react-router-dom`.

// src/components/MyComponent.js
import React from 'react';
import { Link } from 'react-router-dom';

function MyComponent() {
  return (
    <div>
      <Link to="/about">Eiti į Apie puslapį</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('atvaizduoja nuorodą į apie puslapį', () => {
  render(
    <MemoryRouter>
      <MyComponent />
    </MemoryRouter>
  );

  const linkElement = screen.getByRole('link', { name: /Eiti į Apie puslapį/i });
  expect(linkElement).toBeInTheDocument();
  expect(linkElement).toHaveAttribute('href', '/about');
});

Paaiškinimas:

Geriausios praktikos rašant efektyvius testus

Štai keletas geriausių praktikų, kurių reikėtų laikytis rašant testus su RTL:

Išvados

„React Testing Library“ yra galingas įrankis, skirtas rašyti efektyvius, palaikomus ir į vartotoją orientuotus testus jūsų „React“ programoms. Laikydamiesi šiame vadove aprašytų principų ir technikų, galite kurti patikimas ir patikimas programas, atitinkančias jūsų vartotojų poreikius. Nepamirškite sutelkti dėmesį į testavimą iš vartotojo perspektyvos, vengti testuoti įgyvendinimo detales ir rašyti aiškius bei glaustus testus. Pasitelkdami RTL ir taikydami geriausias praktikas, galite žymiai pagerinti savo „React“ projektų kokybę ir palaikomumą, nepriklausomai nuo jūsų buvimo vietos ar specifinių jūsų pasaulinės auditorijos reikalavimų.