Slovenščina

Obvladajte React Testing Library (RTL) s tem popolnim vodnikom. Naučite se pisati učinkovite, vzdržljive in uporabniško usmerjene teste za vaše aplikacije React.

React Testing Library: Celovit vodnik

V današnjem hitro razvijajočem se svetu spletnega razvoja je zagotavljanje kakovosti in zanesljivosti vaših aplikacij React ključnega pomena. React Testing Library (RTL) se je uveljavila kot priljubljena in učinkovita rešitev za pisanje testov, ki se osredotočajo na uporabniško perspektivo. Ta vodnik ponuja celovit pregled RTL, od temeljnih konceptov do naprednih tehnik, s katerim boste lahko gradili robustne in vzdržljive aplikacije React.

Zakaj izbrati React Testing Library?

Tradicionalni pristopi k testiranju se pogosto zanašajo na podrobnosti implementacije, zaradi česar so testi krhki in se zlahka zlomijo ob manjših spremembah kode. RTL pa vas spodbuja, da testirate svoje komponente tako, kot bi z njimi komuniciral uporabnik, s poudarkom na tem, kar uporabnik vidi in doživi. Ta pristop ponuja več ključnih prednosti:

Nastavitev testnega okolja

Preden lahko začnete uporabljati RTL, morate nastaviti svoje testno okolje. To običajno vključuje namestitev potrebnih odvisnosti in konfiguracijo vašega testnega ogrodja.

Predpogoji

Namestitev

Namestite naslednje pakete z uporabo npm ali yarn:

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

Ali, z uporabo yarn:

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

Pojasnilo paketov:

Konfiguracija

Ustvarite datoteko `babel.config.js` v korenski mapi vašega projekta z naslednjo vsebino:

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

Posodobite svojo datoteko `package.json`, da vključite skript za testiranje:

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

Ustvarite datoteko `jest.config.js` v korenski mapi vašega projekta za konfiguracijo Jesta. Minimalna konfiguracija bi lahko izgledala takole:

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

Ustvarite datoteko `src/setupTests.js` z naslednjo vsebino. To zagotavlja, da so Jest DOM primerjalniki na voljo v vseh vaših testih:

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

Pisanje prvega testa

Začnimo s preprostim primerom. Recimo, da imate komponento React, ki prikazuje pozdravno sporočilo:

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

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

export default Greeting;

Zdaj pa napišimo test za to komponento:

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

Pojasnilo:

Za zagon testa v terminalu izvedite naslednji ukaz:

npm test

Če je vse pravilno nastavljeno, bi moral test uspeti.

Pogoste RTL poizvedbe

RTL ponuja različne metode poizvedb za iskanje elementov v DOM-u. Te poizvedbe so zasnovane tako, da posnemajo, kako uporabniki komunicirajo z vašo aplikacijo.

`getByRole`

Ta poizvedba najde element po njegovi ARIA vlogi. Dobra praksa je, da uporabljate `getByRole` kadar koli je to mogoče, saj spodbuja dostopnost in zagotavlja, da so vaši testi odporni na spremembe v osnovni strukturi DOM-a.

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

`getByLabelText`

Ta poizvedba najde element po besedilu pripadajoče oznake. Uporabna je za testiranje elementov obrazcev.

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

`getByPlaceholderText`

Ta poizvedba najde element po njegovem nadomestnem besedilu (placeholder).

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

`getByAltText`

Ta poizvedba najde slikovni element po njegovem alt besedilu. Pomembno je, da za vse slike zagotovite smiselno alt besedilo, da zagotovite dostopnost.

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

`getByTitle`

Ta poizvedba najde element po njegovem atributu `title`.

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

`getByDisplayValue`

Ta poizvedba najde element po njegovi prikazani vrednosti. To je uporabno za testiranje vnosnih polj obrazcev z vnaprej izpolnjenimi vrednostmi.

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

Poizvedbe `getAllBy*`

Poleg poizvedb `getBy*` RTL ponuja tudi poizvedbe `getAllBy*`, ki vrnejo polje ustreznih elementov. Te so uporabne, ko želite potrditi, da je v DOM-u prisotnih več elementov z enakimi značilnostmi.

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

Poizvedbe `queryBy*`

Poizvedbe `queryBy*` so podobne poizvedbam `getBy*`, vendar vrnejo `null`, če noben ustrezen element ni najden, namesto da bi sprožile napako. To je uporabno, ko želite potrditi, da element *ni* prisoten v DOM-u.

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

Poizvedbe `findBy*`

Poizvedbe `findBy*` so asinhrone različice poizvedb `getBy*`. Vrnejo `Promise`, ki se razreši, ko je ustrezen element najden. Te so uporabne za testiranje asinhronih operacij, kot je pridobivanje podatkov iz API-ja.

// Simulacija asinhronega pridobivanja podatkov
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();
});

Simuliranje interakcij uporabnika

RTL ponuja API-ja `fireEvent` in `userEvent` za simuliranje interakcij uporabnika, kot so klikanje gumbov, tipkanje v vnosna polja in pošiljanje obrazcev.

`fireEvent`

`fireEvent` vam omogoča programsko sprožanje DOM dogodkov. Je API nižje ravni, ki vam daje natančen nadzor nad sproženimi dogodki.

<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` je API višje ravni, ki bolj realistično simulira interakcije uporabnika. Upravlja podrobnosti, kot so upravljanje fokusa in vrstni red dogodkov, kar naredi vaše teste bolj robustne in manj krhke.

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

Testiranje asinhrone kode

Številne aplikacije React vključujejo asinhrone operacije, kot je pridobivanje podatkov iz API-ja. RTL ponuja več orodij za testiranje asinhrone kode.

`waitFor`

`waitFor` vam omogoča, da počakate, da pogoj postane resničen, preden postavite trditev. Uporabno je za testiranje asinhronih operacij, ki za dokončanje potrebujejo nekaj časa.

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

Poizvedbe `findBy*`

Kot smo že omenili, so poizvedbe `findBy*` asinhrone in vrnejo `Promise`, ki se razreši, ko je ustrezen element najden. Te so uporabne za testiranje asinhronih operacij, ki povzročijo spremembe v DOM-u.

Testiranje Hookov

React Hooki so ponovno uporabljive funkcije, ki enkapsulirajo logiko s stanjem. RTL ponuja pripomoček `renderHook` iz `@testing-library/react-hooks` (ki je od različice v17 opuščen v korist `@testing-library/react` neposredno) za testiranje Hookov po meri v izolaciji.

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

Pojasnilo:

Napredne tehnike testiranja

Ko obvladate osnove RTL, lahko raziščete naprednejše tehnike testiranja za izboljšanje kakovosti in vzdržljivosti vaših testov.

Mockiranje modulov

Včasih boste morda morali mockirati zunanje module ali odvisnosti, da izolirate svoje komponente in nadzorujete njihovo obnašanje med testiranjem. Jest za ta namen ponuja zmogljiv API za mockiranje.

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

Pojasnilo:

Context Providerji

Če je vaša komponenta odvisna od Context Providerja, boste med testiranjem morali svojo komponento oviti v ta provider. To zagotavlja, da ima komponenta dostop do vrednosti konteksta.

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

Pojasnilo:

Testiranje z Routerjem

Pri testiranju komponent, ki uporabljajo React Router, boste morali zagotoviti mockiran kontekst Routerja. To lahko dosežete z uporabo komponente `MemoryRouter` iz `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');
});

Pojasnilo:

Najboljše prakse za pisanje učinkovitih testov

Tukaj je nekaj najboljših praks, ki jih je treba upoštevati pri pisanju testov z RTL:

Zaključek

React Testing Library je zmogljivo orodje za pisanje učinkovitih, vzdržljivih in uporabniško usmerjenih testov za vaše aplikacije React. Z upoštevanjem načel in tehnik, opisanih v tem vodniku, lahko gradite robustne in zanesljive aplikacije, ki ustrezajo potrebam vaših uporabnikov. Ne pozabite se osredotočiti na testiranje z vidika uporabnika, izogibajte se testiranju podrobnosti implementacije in pišite jasne ter jedrnate teste. S sprejetjem RTL in upoštevanjem najboljših praks lahko bistveno izboljšate kakovost in vzdržljivost svojih projektov React, ne glede na vašo lokacijo ali specifične zahteve vaše globalne publike.

React Testing Library: Celovit vodnik | MLOG