Latviešu

Apgūstiet React Testing Library (RTL) ar šo pilnīgo ceļvedi. Uzziniet, kā rakstīt efektīvus, uzturamus un uz lietotāju orientētus testus savām React lietotnēm, koncentrējoties uz labāko praksi un reāliem piemēriem.

React Testing Library: Visaptverošs Ceļvedis

Mūsdienu straujajā tīmekļa izstrādes vidē jūsu React lietotņu kvalitātes un uzticamības nodrošināšana ir vissvarīgākā. React Testing Library (RTL) ir kļuvusi par populāru un efektīvu risinājumu, lai rakstītu testus, kas vērsti uz lietotāja perspektīvu. Šis ceļvedis sniedz pilnīgu pārskatu par RTL, aptverot visu, sākot no pamatkoncepcijām līdz pat progresīvām tehnikām, dodot jums iespēju veidot robustas un uzturamas React lietotnes.

Kāpēc izvēlēties React Testing Library?

Tradicionālās testēšanas pieejas bieži balstās uz implementācijas detaļām, padarot testus trauslus un pakļautus lūšanai pie nelielām koda izmaiņām. Savukārt RTL mudina jūs testēt savus komponentus tā, kā ar tiem mijiedarbotos lietotājs, koncentrējoties uz to, ko lietotājs redz un piedzīvo. Šī pieeja piedāvā vairākas galvenās priekšrocības:

Testēšanas vides iestatīšana

Pirms varat sākt lietot RTL, jums ir jāiestata sava testēšanas vide. Tas parasti ietver nepieciešamo atkarību instalēšanu un testēšanas ietvara konfigurēšanu.

Priekšnosacījumi

Instalēšana

Instalējiet šādas pakotnes, izmantojot npm vai yarn:

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

Vai, izmantojot yarn:

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

Paku skaidrojums:

Konfigurācija

Izveidojiet `babel.config.js` failu sava projekta saknes direktorijā ar šādu saturu:

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

Atjauniniet savu `package.json` failu, lai iekļautu testēšanas skriptu:

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

Izveidojiet `jest.config.js` failu sava projekta saknes direktorijā, lai konfigurētu Jest. Minimāla konfigurācija varētu izskatīties šādi:

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

Izveidojiet `src/setupTests.js` failu ar šādu saturu. Tas nodrošina, ka Jest DOM saskaņotāji ir pieejami visos jūsu testos:

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

Pirmā testa rakstīšana

Sāksim ar vienkāršu piemēru. Pieņemsim, ka jums ir React komponents, kas parāda sveiciena ziņojumu:

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

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

export default Greeting;

Tagad uzrakstīsim testu šim komponentam:

// 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();
});

Skaidrojums:

Lai palaistu testu, izpildiet šādu komandu savā terminālī:

npm test

Ja viss ir pareizi konfigurēts, testam vajadzētu būt sekmīgam.

Biežākās RTL vaicājumu metodes

RTL nodrošina dažādas vaicājumu metodes elementu atrašanai DOM. Šīs metodes ir izstrādātas, lai atdarinātu, kā lietotāji mijiedarbojas ar jūsu lietotni.

`getByRole`

Šis vaicājums atrod elementu pēc tā ARIA lomas. Ir laba prakse izmantot `getByRole`, kad vien iespējams, jo tas veicina pieejamību un nodrošina, ka jūsu testi ir noturīgi pret izmaiņām pamatā esošajā DOM struktūrā.

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

`getByLabelText`

Šis vaicājums atrod elementu pēc tā saistītās etiķetes teksta. Tas ir noderīgs formu elementu testēšanai.

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

`getByPlaceholderText`

Šis vaicājums atrod elementu pēc tā viettura teksta.

<input type="text" placeholder="Enter your email" />
const emailInputElement = screen.getByPlaceholderText('Enter your email');
expect(emailInputElement).toBeInTheDocument();

`getByAltText`

Šis vaicājums atrod attēla elementu pēc tā alternatīvā teksta (alt text). Ir svarīgi nodrošināt jēgpilnu alternatīvo tekstu visiem attēliem, lai nodrošinātu pieejamību.

<img src="logo.png" alt="Company Logo" />
const logoImageElement = screen.getByAltText('Company Logo');
expect(logoImageElement).toBeInTheDocument();

`getByTitle`

Šis vaicājums atrod elementu pēc tā `title` atribūta.

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

`getByDisplayValue`

Šis vaicājums atrod elementu pēc tā parādītās vērtības. Tas ir noderīgs formu ievades lauku testēšanai ar iepriekš aizpildītām vērtībām.

<input type="text" value="Initial Value" />
const inputElement = screen.getByDisplayValue('Initial Value');
expect(inputElement).toBeInTheDocument();

`getAllBy*` vaicājumi

Papildus `getBy*` vaicājumiem, RTL nodrošina arī `getAllBy*` vaicājumus, kas atgriež atbilstošo elementu masīvu. Tie ir noderīgi, ja jums jāapgalvo, ka DOM ir vairāki elementi ar vienādām īpašībām.

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

`queryBy*` vaicājumi

`queryBy*` vaicājumi ir līdzīgi `getBy*` vaicājumiem, bet tie atgriež `null`, ja netiek atrasts atbilstošs elements, nevis met kļūdu. Tas ir noderīgi, ja vēlaties apgalvot, ka elements *nav* klāt DOM.

const missingElement = screen.queryByText('Non-existent text');
expect(missingElement).toBeNull();

`findBy*` vaicājumi

`findBy*` vaicājumi ir `getBy*` vaicājumu asinhronās versijas. Tie atgriež solījumu (Promise), kas tiek atrisināts, kad tiek atrasts atbilstošais elements. Tie ir noderīgi asinhronu darbību testēšanai, piemēram, datu ielādei no API.

// 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();
});

Lietotāja mijiedarbību simulēšana

RTL nodrošina `fireEvent` un `userEvent` API, lai simulētu lietotāja mijiedarbības, piemēram, pogu klikšķināšanu, teksta ievadi ievades laukos un formu iesniegšanu.

`fireEvent`

`fireEvent` ļauj programmatiski izraisīt DOM notikumus. Tā ir zemāka līmeņa API, kas sniedz jums precīzu kontroli pār izsauktajiem notikumiem.

<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` ir augstāka līmeņa API, kas reālistiskāk simulē lietotāja mijiedarbības. Tā apstrādā tādas detaļas kā fokusa pārvaldība un notikumu secība, padarot jūsu testus robustākus un mazāk trauslus.

<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!');
});

Asinhronā koda testēšana

Daudzas React lietotnes ietver asinhronas darbības, piemēram, datu ielādi no API. RTL nodrošina vairākus rīkus asinhronā koda testēšanai.

`waitFor`

`waitFor` ļauj jums gaidīt, līdz nosacījums kļūst patiess, pirms veikt apgalvojumu. Tas ir noderīgs, testējot asinhronas darbības, kuru pabeigšanai nepieciešams zināms laiks.

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*` vaicājumi

Kā minēts iepriekš, `findBy*` vaicājumi ir asinhroni un atgriež solījumu (Promise), kas tiek atrisināts, kad tiek atrasts atbilstošais elements. Tie ir noderīgi, testējot asinhronas darbības, kas izraisa izmaiņas DOM.

"Hooks" testēšana

React "Hooks" ir atkārtoti lietojamas funkcijas, kas iekapsulē stāvokļa loģiku. RTL nodrošina `renderHook` utilītu no `@testing-library/react-hooks` (kas ir novecojusi un aizstāta ar `@testing-library/react` tieši no v17), lai testētu pielāgotus "Hooks" izolēti.

// 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);
});

Skaidrojums:

Padziļinātas testēšanas tehnikas

Kad esat apguvis RTL pamatus, varat izpētīt progresīvākas testēšanas tehnikas, lai uzlabotu savu testu kvalitāti un uzturamību.

Moduļu imitēšana (Mocking)

Dažreiz jums var būt nepieciešams imitēt ārējus moduļus vai atkarības, lai izolētu savus komponentus un kontrolētu to uzvedību testēšanas laikā. Jest nodrošina spēcīgu imitēšanas API šim nolūkam.

// 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);
});

Skaidrojums:

Konteksta nodrošinātāji (Context Providers)

Ja jūsu komponents paļaujas uz Konteksta nodrošinātāju (Context Provider), jums testēšanas laikā būs jāietin jūsu komponents šajā nodrošinātājā. Tas nodrošina, ka komponentam ir piekļuve konteksta vērtībām.

// 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();
});

Skaidrojums:

Testēšana ar maršrutētāju (Router)

Testējot komponentus, kas izmanto React Router, jums būs jānodrošina imitēts maršrutētāja konteksts. To var panākt, izmantojot `MemoryRouter` komponentu no `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');
});

Skaidrojums:

Labākās prakses efektīvu testu rakstīšanai

Šeit ir dažas labākās prakses, kas jāievēro, rakstot testus ar RTL:

Noslēgums

React Testing Library ir spēcīgs rīks, lai rakstītu efektīvus, uzturamus un uz lietotāju orientētus testus jūsu React lietotnēm. Sekojot šajā ceļvedī izklāstītajiem principiem un tehnikām, jūs varat veidot robustas un uzticamas lietotnes, kas atbilst jūsu lietotāju vajadzībām. Atcerieties koncentrēties uz testēšanu no lietotāja perspektīvas, izvairīties no implementācijas detaļu testēšanas un rakstīt skaidrus un kodolīgus testus. Pieņemot RTL un labākās prakses, jūs varat ievērojami uzlabot savu React projektu kvalitāti un uzturamību, neatkarīgi no jūsu atrašanās vietas vai jūsu globālās auditorijas īpašajām prasībām.