Bahasa Indonesia

Kuasai React Testing Library (RTL) dengan panduan lengkap ini. Pelajari cara menulis pengujian yang efektif, mudah dipelihara, dan berpusat pada pengguna untuk aplikasi React Anda, dengan fokus pada praktik terbaik dan contoh dunia nyata.

React Testing Library: Panduan Komprehensif

Dalam lanskap pengembangan web yang serba cepat saat ini, memastikan kualitas dan keandalan aplikasi React Anda adalah hal yang terpenting. React Testing Library (RTL) telah muncul sebagai solusi populer dan efektif untuk menulis pengujian yang berfokus pada perspektif pengguna. Panduan ini memberikan gambaran lengkap tentang RTL, mencakup segalanya mulai dari konsep dasar hingga teknik-teknik canggih, memberdayakan Anda untuk membangun aplikasi React yang kuat dan mudah dipelihara.

Mengapa Memilih React Testing Library?

Pendekatan pengujian tradisional sering kali bergantung pada detail implementasi, membuat pengujian menjadi rapuh dan rentan rusak dengan perubahan kode kecil. RTL, di sisi lain, mendorong Anda untuk menguji komponen Anda seolah-olah pengguna berinteraksi dengannya, berfokus pada apa yang dilihat dan dialami pengguna. Pendekatan ini menawarkan beberapa keuntungan utama:

Menyiapkan Lingkungan Pengujian Anda

Sebelum Anda dapat mulai menggunakan RTL, Anda perlu menyiapkan lingkungan pengujian Anda. Ini biasanya melibatkan instalasi dependensi yang diperlukan dan mengonfigurasi kerangka kerja pengujian Anda.

Prasyarat

Instalasi

Instal paket-paket berikut menggunakan npm atau yarn:

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

Atau, menggunakan yarn:

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

Penjelasan Paket:

Konfigurasi

Buat file `babel.config.js` di direktori root proyek Anda dengan konten berikut:

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

Perbarui file `package.json` Anda untuk menyertakan skrip pengujian:

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

Buat file `jest.config.js` di direktori root proyek Anda untuk mengonfigurasi Jest. Konfigurasi minimal mungkin terlihat seperti ini:

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

Buat file `src/setupTests.js` dengan konten berikut. Ini memastikan bahwa pencocok Jest DOM tersedia di semua pengujian Anda:

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

Menulis Tes Pertama Anda

Mari kita mulai dengan contoh sederhana. Misalkan Anda memiliki komponen React yang menampilkan pesan sapaan:

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

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

export default Greeting;

Sekarang, mari kita tulis tes untuk komponen ini:

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

test('merender pesan sapaan', () => {
  render(<Greeting name="World" />);
  const greetingElement = screen.getByText(/Hello, World!/i);
  expect(greetingElement).toBeInTheDocument();
});

Penjelasan:

Untuk menjalankan tes, jalankan perintah berikut di terminal Anda:

npm test

Jika semuanya dikonfigurasi dengan benar, tes seharusnya berhasil.

Kueri RTL yang Umum

RTL menyediakan berbagai metode kueri untuk menemukan elemen di dalam DOM. Kueri-kueri ini dirancang untuk meniru bagaimana pengguna berinteraksi dengan aplikasi Anda.

`getByRole`

Kueri ini menemukan elemen berdasarkan peran ARIA-nya. Merupakan praktik yang baik untuk menggunakan `getByRole` kapan pun memungkinkan, karena ini mempromosikan aksesibilitas dan memastikan bahwa tes Anda tangguh terhadap perubahan dalam struktur DOM yang mendasarinya.

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

`getByLabelText`

Kueri ini menemukan elemen berdasarkan teks dari label yang terkait. Ini berguna untuk menguji elemen formulir.

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

`getByPlaceholderText`

Kueri ini menemukan elemen berdasarkan teks placeholder-nya.

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

`getByAltText`

Kueri ini menemukan elemen gambar berdasarkan teks alt-nya. Penting untuk menyediakan teks alt yang bermakna untuk semua gambar untuk memastikan aksesibilitas.

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

`getByTitle`

Kueri ini menemukan elemen berdasarkan atribut judulnya.

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

`getByDisplayValue`

Kueri ini menemukan elemen berdasarkan nilai tampilannya. Ini berguna untuk menguji input formulir dengan nilai yang sudah diisi sebelumnya.

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

Kueri `getAllBy*`

Selain kueri `getBy*`, RTL juga menyediakan kueri `getAllBy*`, yang mengembalikan array elemen yang cocok. Ini berguna ketika Anda perlu menegaskan bahwa beberapa elemen dengan karakteristik yang sama ada di dalam DOM.

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

Kueri `queryBy*`

Kueri `queryBy*` mirip dengan kueri `getBy*`, tetapi mereka mengembalikan `null` jika tidak ada elemen yang cocok yang ditemukan, alih-alih melempar kesalahan. Ini berguna ketika Anda ingin menegaskan bahwa sebuah elemen *tidak* ada di dalam DOM.

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

Kueri `findBy*`

Kueri `findBy*` adalah versi asinkron dari kueri `getBy*`. Mereka mengembalikan Promise yang diselesaikan ketika elemen yang cocok ditemukan. Ini berguna untuk menguji operasi asinkron, seperti mengambil data dari API.

// Mensimulasikan pengambilan data asinkron
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('memuat data secara asinkron', async () => {
  render(<MyComponent />);
  const dataElement = await screen.findByText('Data Loaded!');
  expect(dataElement).toBeInTheDocument();
});

Mensimulasikan Interaksi Pengguna

RTL menyediakan API `fireEvent` dan `userEvent` untuk mensimulasikan interaksi pengguna, seperti mengklik tombol, mengetik di bidang input, dan mengirimkan formulir.

`fireEvent`

`fireEvent` memungkinkan Anda untuk memicu event DOM secara terprogram. Ini adalah API tingkat rendah yang memberi Anda kontrol terperinci atas event yang dipicu.

<button onClick={() => alert('Button clicked!')}>Click me</button>
import { fireEvent } from '@testing-library/react';

test('mensimulasikan klik tombol', () => {
  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` adalah API tingkat tinggi yang mensimulasikan interaksi pengguna dengan lebih realistis. Ini menangani detail seperti manajemen fokus dan urutan event, membuat tes Anda lebih kuat dan tidak rapuh.

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

test('mensimulasikan pengetikan di bidang input', () => {
  const inputElement = screen.getByRole('textbox');
  userEvent.type(inputElement, 'Hello, world!');
  expect(inputElement).toHaveValue('Hello, world!');
});

Menguji Kode Asinkron

Banyak aplikasi React melibatkan operasi asinkron, seperti mengambil data dari API. RTL menyediakan beberapa alat untuk menguji kode asinkron.

`waitFor`

`waitFor` memungkinkan Anda menunggu hingga suatu kondisi menjadi benar sebelum membuat asersi. Ini berguna untuk menguji operasi asinkron yang membutuhkan waktu untuk selesai.

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('menunggu data dimuat', async () => {
  render(<MyComponent />);
  await waitFor(() => screen.getByText('Data loaded!'));
  const dataElement = screen.getByText('Data loaded!');
  expect(dataElement).toBeInTheDocument();
});

Kueri `findBy*`

Seperti yang disebutkan sebelumnya, kueri `findBy*` bersifat asinkron dan mengembalikan Promise yang diselesaikan ketika elemen yang cocok ditemukan. Ini berguna untuk menguji operasi asinkron yang menghasilkan perubahan pada DOM.

Menguji Hooks

React Hooks adalah fungsi yang dapat digunakan kembali yang mengenkapsulasi logika stateful. RTL menyediakan utilitas `renderHook` dari `@testing-library/react-hooks` (yang sudah usang dan digantikan langsung oleh `@testing-library/react` per v17) untuk menguji Hooks kustom secara terisolasi.

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

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

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

Penjelasan:

Teknik Pengujian Tingkat Lanjut

Setelah Anda menguasai dasar-dasar RTL, Anda dapat menjelajahi teknik pengujian yang lebih canggih untuk meningkatkan kualitas dan kemudahan pemeliharaan tes Anda.

Melakukan Mocking pada Modul

Terkadang, Anda mungkin perlu melakukan mock pada modul atau dependensi eksternal untuk mengisolasi komponen Anda dan mengontrol perilakunya selama pengujian. Jest menyediakan API mocking yang kuat untuk tujuan ini.

// 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('mengambil data dari 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);
});

Penjelasan:

Penyedia Konteks (Context Providers)

Jika komponen Anda bergantung pada Penyedia Konteks (Context Provider), Anda perlu membungkus komponen Anda di dalam provider tersebut selama pengujian. Ini memastikan bahwa komponen memiliki akses ke nilai-nilai konteks.

// 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('mengganti tema', () => {
  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();
});

Penjelasan:

Pengujian dengan Router

Saat menguji komponen yang menggunakan React Router, Anda perlu menyediakan konteks Router tiruan (mock). Anda dapat mencapainya dengan menggunakan komponen `MemoryRouter` dari `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('merender tautan ke halaman tentang', () => {
  render(
    <MemoryRouter>
      <MyComponent />
    </MemoryRouter>
  );

  const linkElement = screen.getByRole('link', { name: /Go to About Page/i });
  expect(linkElement).toBeInTheDocument();
  expect(linkElement).toHaveAttribute('href', '/about');
});

Penjelasan:

Praktik Terbaik untuk Menulis Tes yang Efektif

Berikut adalah beberapa praktik terbaik yang harus diikuti saat menulis tes dengan RTL:

Kesimpulan

React Testing Library adalah alat yang ampuh untuk menulis tes yang efektif, mudah dipelihara, dan berpusat pada pengguna untuk aplikasi React Anda. Dengan mengikuti prinsip dan teknik yang diuraikan dalam panduan ini, Anda dapat membangun aplikasi yang kuat dan andal yang memenuhi kebutuhan pengguna Anda. Ingatlah untuk fokus pada pengujian dari perspektif pengguna, hindari menguji detail implementasi, dan tulis tes yang jelas dan ringkas. Dengan merangkul RTL dan mengadopsi praktik terbaik, Anda dapat secara signifikan meningkatkan kualitas dan kemudahan pemeliharaan proyek React Anda, terlepas dari lokasi Anda atau persyaratan spesifik dari audiens global Anda.