Jest: Cara Memverifikasi Elemen Tidak Ada dalam Output yang Dirender
Dalam pengujian komponen React menggunakan Jest dan React Testing Library, salah satu skenario umum adalah memverifikasi bahwa elemen tertentu tidak muncul dalam output yang dirender. Hal ini penting untuk menguji logika bersyarat, visibilitas elemen berdasarkan status, atau penanganan kesalahan. Artikel ini membahas berbagai cara untuk mencapai hal ini secara efektif, lengkap dengan contoh kode dan praktik terbaik.
Daftar Isi
- Pendahuluan: Mengapa Verifikasi Ketidakadaan Elemen Penting?
- Metode untuk Memverifikasi Ketidakadaan Elemen
- Praktik Terbaik untuk Menulis Pengujian yang Efektif
- Contoh yang Lebih Kompleks: Menangani Rendering Bersyarat
- Pemecahan Masalah Umum
- Kesimpulan
1. Pendahuluan: Mengapa Verifikasi Ketidakadaan Elemen Penting?
Dalam pengembangan antarmuka pengguna, seringkali elemen hanya muncul di layar di bawah kondisi tertentu. Kondisi ini bisa berupa:
- Status aplikasi (misalnya, pesan kesalahan hanya ditampilkan jika ada kesalahan).
- Interaksi pengguna (misalnya, menu dropdown muncul saat tombol diklik).
- Data yang diambil dari server (misalnya, pesan “Tidak ada data” ditampilkan jika tidak ada data yang tersedia).
Oleh karena itu, penting untuk dapat memverifikasi bahwa elemen tidak ada saat kondisi yang sesuai tidak terpenuhi. Tanpa pengujian semacam itu, Anda mungkin secara keliru menganggap bahwa aplikasi Anda berperilaku seperti yang diharapkan, padahal sebenarnya, elemen yang tidak seharusnya ada, ditampilkan, yang dapat membingungkan atau bahkan menyesatkan pengguna.
Verifikasi ketidakadaan elemen memungkinkan kita untuk memastikan bahwa:
- Logika bersyarat berfungsi dengan benar.
- Komponen merender keadaan yang benar berdasarkan status aplikasi.
- Penanganan kesalahan berfungsi seperti yang diharapkan.
2. Metode untuk Memverifikasi Ketidakadaan Elemen
React Testing Library menyediakan berbagai cara untuk mengkueri elemen dalam DOM. Untuk memverifikasi ketidakadaan elemen, kita biasanya menggunakan varian “queryBy” daripada varian “getBy”. Perbedaan utamanya adalah:
getBy*
: Melempar kesalahan jika elemen tidak ditemukan. Ini bagus untuk menegaskan bahwa elemen harus ada.queryBy*
: Mengembalikannull
jika elemen tidak ditemukan. Ini ideal untuk menegaskan bahwa elemen seharusnya tidak ada.findAllBy*
&findAllBy*
: Asinkronus, mengembalikan Promises dan digunakan saat elemen mungkin belum langsung ada.
Berikut adalah beberapa metode yang umum digunakan:
2.1. Menggunakan queryByText
Metode ini mencari elemen berdasarkan teks yang terkandung di dalamnya.
Contoh:
“`javascript
import { render, screen } from ‘@testing-library/react’;
import MyComponent from ‘./MyComponent’;
test(‘pesan error tidak ditampilkan saat tidak ada error’, () => {
render(
const errorMessage = screen.queryByText(‘Terjadi kesalahan.’);
expect(errorMessage).toBeNull();
});
“`
Dalam contoh ini, kita merender MyComponent
dan kemudian menggunakan screen.queryByText('Terjadi kesalahan.')
untuk mencari elemen yang berisi teks “Terjadi kesalahan.”. Jika elemen tidak ditemukan, queryByText
akan mengembalikan null
, dan pernyataan expect(errorMessage).toBeNull()
akan berhasil.
2.2. Menggunakan queryByRole
Metode ini mencari elemen berdasarkan peran ARIA-nya (Accessible Rich Internet Applications). Ini adalah cara yang baik untuk menguji aksesibilitas aplikasi Anda.
Contoh:
“`javascript
import { render, screen } from ‘@testing-library/react’;
import MyComponent from ‘./MyComponent’;
test(‘tombol tutup tidak ditampilkan saat modal tidak terbuka’, () => {
render(
const closeButton = screen.queryByRole(‘button’, { name: ‘Tutup’ });
expect(closeButton).toBeNull();
});
“`
Dalam contoh ini, kita mencari tombol dengan peran “button” dan nama “Tutup”. Jika tombol tidak ditemukan, queryByRole
akan mengembalikan null
.
2.3. Menggunakan queryByTestId
Metode ini mencari elemen berdasarkan atribut data-testid
. Ini berguna untuk mengidentifikasi elemen secara unik dalam pengujian, terutama ketika elemen tersebut tidak memiliki teks atau peran ARIA yang unik.
Peringatan: Gunakan data-testid
dengan hemat. Idealnya, Anda harus menguji berdasarkan apa yang dilihat pengguna (teks, peran, dll.). Gunakan data-testid
hanya jika tidak ada cara lain untuk mengidentifikasi elemen tersebut.
Contoh:
“`javascript
import { render, screen } from ‘@testing-library/react’;
import MyComponent from ‘./MyComponent’;
test(‘indicator loading tidak ditampilkan saat data sudah selesai dimuat’, () => {
render(
const loadingIndicator = screen.queryByTestId(‘loading-indicator’);
expect(loadingIndicator).toBeNull();
});
“`
Dalam contoh ini, kita mencari elemen dengan atribut data-testid="loading-indicator"
.
2.4. Menggunakan queryAllBy*
Metode ini berguna saat Anda ingin memastikan bahwa tidak ada elemen yang cocok dengan kriteria tertentu. Contohnya, Anda ingin memastikan bahwa tidak ada pesan kesalahan yang ditampilkan jika tidak ada kesalahan.
Contoh:
“`javascript
import { render, screen } from ‘@testing-library/react’;
import MyComponent from ‘./MyComponent’;
test(‘tidak ada pesan error yang ditampilkan saat tidak ada error’, () => {
render(
const errorMessages = screen.queryAllByText(‘Terjadi kesalahan.’);
expect(errorMessages).toHaveLength(0);
});
“`
Dalam contoh ini, kita menggunakan queryAllByText
untuk mencari semua elemen yang berisi teks “Terjadi kesalahan.”. Kita kemudian menegaskan bahwa panjang array yang dikembalikan adalah 0, yang berarti tidak ada elemen yang ditemukan.
2.5. Menggabungkan dengan not.toBeInTheDocument()
Meskipun queryBy*
mengembalikan null
jika tidak ditemukan, Anda juga dapat menggunakan getBy*
(yang melemparkan kesalahan jika tidak ditemukan) dan menggabungkannya dengan not.toBeInTheDocument()
dari Jest DOM.
Contoh:
“`javascript
import { render, screen } from ‘@testing-library/react’;
import MyComponent from ‘./MyComponent’;
test(‘pesan error tidak ditampilkan saat tidak ada error’, () => {
render(
expect(screen.getByText(‘Terjadi kesalahan.’)).not.toBeInTheDocument();
});
“`
Perhatian: Penggunaan getByText
akan melempar error jika elemen tidak ditemukan. Untuk menghindari error tersebut saat melakukan pengujian `negative`, maka fungsi harus dibungkus dengan `expect`.
“`javascript
import { render, screen } from ‘@testing-library/react’;
import MyComponent from ‘./MyComponent’;
test(‘pesan error tidak ditampilkan saat tidak ada error’, () => {
render(
expect(() => screen.getByText(‘Terjadi kesalahan.’)).toThrow();
});
“`
Dalam contoh ini, kita mencoba mendapatkan elemen dengan teks “Terjadi kesalahan.” menggunakan screen.getByText()
. Jika elemen tidak ditemukan, maka Jest akan melempar error dan `expect(() => screen.getByText(‘Terjadi kesalahan.’)).toThrow();` akan menangkap error tersebut, sehingga pengujian akan berhasil. Jika elemen ditemukan, `getByText` tidak melempar error, dan pengujian akan gagal.
Metode ini kurang disarankan dibandingkan menggunakan queryBy*
karena bergantung pada penanganan kesalahan, yang bisa kurang jelas.
3. Praktik Terbaik untuk Menulis Pengujian yang Efektif
Berikut adalah beberapa praktik terbaik untuk menulis pengujian yang efektif dan mudah dipelihara:
3.1. Nama Pengujian yang Deskriptif
Nama pengujian harus secara jelas menyatakan apa yang sedang diuji. Ini memudahkan untuk memahami apa yang salah jika pengujian gagal.
Contoh:
Buruk:
“`javascript
test(‘test’, () => {
// …
});
“`
Baik:
“`javascript
test(‘pesan error tidak ditampilkan saat tidak ada error’, () => {
// …
});
“`
3.2. Hindari Pemilih yang Rapuh
Hindari menggunakan pemilih yang bergantung pada struktur DOM yang tepat. Misalnya, hindari menggunakan indeks elemen (misalnya, document.querySelectorAll('div')[2]
). Ini karena perubahan kecil pada struktur DOM dapat menyebabkan pengujian Anda gagal.
Sebagai gantinya, gunakan pemilih yang lebih stabil, seperti:
- Teks (
getByText
,queryByText
). - Peran ARIA (
getByRole
,queryByRole
). - Atribut
data-testid
(hanya jika diperlukan).
3.3. Fokus pada Perilaku, Bukan Implementasi
Pengujian Anda harus fokus pada pengujian perilaku komponen, bukan detail implementasinya. Ini berarti Anda harus menguji apa yang dilihat dan dialami pengguna, bukan bagaimana komponen diimplementasikan.
Contoh:
Daripada menguji bahwa fungsi internal tertentu dipanggil, uji bahwa komponen merender output yang benar berdasarkan interaksi pengguna.
3.4. Gunakan userEvent
untuk Interaksi
Gunakan pustaka userEvent
untuk mensimulasikan interaksi pengguna (misalnya, klik, ketik). Ini memastikan bahwa komponen Anda bereaksi terhadap interaksi seperti yang diharapkan oleh pengguna.
Contoh:
“`javascript
import { render, screen, userEvent } from ‘@testing-library/react’;
import MyComponent from ‘./MyComponent’;
test(‘pesan error ditampilkan setelah tombol diklik’, async () => {
render(
const button = screen.getByRole(‘button’, { name: ‘Tampilkan Error’ });
await userEvent.click(button);
const errorMessage = screen.getByText(‘Terjadi kesalahan.’);
expect(errorMessage).toBeInTheDocument();
});
“`
3.5. Gunakan Mocking dengan Bijak
Mocking adalah teknik mengganti dependensi eksternal dengan implementasi tiruan. Ini berguna untuk mengisolasi komponen yang sedang diuji dan untuk mengontrol data yang diterimanya.
Namun, gunakan mocking dengan bijak. Terlalu banyak mocking dapat membuat pengujian Anda menjadi tidak realistis dan kurang berguna.
Contoh:
Jika komponen Anda mengambil data dari API, Anda dapat melakukan mocking API untuk mengembalikan data yang dapat Anda kontrol. Ini memungkinkan Anda untuk menguji komponen dalam berbagai skenario (misalnya, data berhasil diambil, data tidak ditemukan, terjadi kesalahan).
4. Contoh yang Lebih Kompleks: Menangani Rendering Bersyarat
Mari kita lihat contoh yang lebih kompleks di mana kita perlu menangani rendering bersyarat.
Misalkan kita memiliki komponen UserProfile
yang menampilkan profil pengguna. Komponen ini menampilkan pesan “Loading…” saat data sedang dimuat, dan menampilkan profil pengguna setelah data selesai dimuat. Jika terjadi kesalahan, komponen menampilkan pesan kesalahan.
“`javascript
import React, { useState, useEffect } from ‘react’;
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchUser() {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(‘Gagal mengambil data pengguna’);
}
const data = await response.json();
setUser(data);
} catch (error) {
setError(error.message);
} finally {
setIsLoading(false);
}
}
fetchUser();
}, [userId]);
if (isLoading) {
return
Loading…
;
}
if (error) {
return
Error: {error}
;
}
return (
{user.name}
{user.email}
);
}
export default UserProfile;
“`
Berikut adalah beberapa pengujian yang dapat kita tulis untuk komponen ini:
“`javascript
import { render, screen, waitFor } from ‘@testing-library/react’;
import UserProfile from ‘./UserProfile’;
// Mock the fetch function
global.fetch = jest.fn();
test(‘menampilkan pesan loading saat data sedang dimuat’, () => {
// Arrange
fetch.mockResolvedValue(Promise.resolve({ok: true, json: () => Promise.resolve({})}));
// Act
render(
// Assert
expect(screen.getByText(‘Loading…’)).toBeInTheDocument();
});
test(‘tidak menampilkan pesan loading setelah data selesai dimuat’, async () => {
// Arrange
fetch.mockResolvedValue(Promise.resolve({ok: true, json: () => Promise.resolve({ name: ‘John Doe’, email: ‘john.doe@example.com’ })}));
// Act
render(
// Assert
await waitFor(() => expect(screen.queryByText(‘Loading…’)).toBeNull());
});
test(‘menampilkan pesan error jika terjadi kesalahan’, async () => {
// Arrange
fetch.mockRejectedValue(new Error(‘Gagal mengambil data pengguna’));
// Act
render(
// Assert
await waitFor(() => expect(screen.getByText(‘Error: Gagal mengambil data pengguna’)).toBeInTheDocument());
});
test(‘tidak menampilkan pesan error jika data berhasil dimuat’, async () => {
// Arrange
fetch.mockResolvedValue(Promise.resolve({ok: true, json: () => Promise.resolve({ name: ‘John Doe’, email: ‘john.doe@example.com’ })}));
// Act
render(
// Assert
await waitFor(() => expect(screen.queryByText(‘Error: Gagal mengambil data pengguna’)).toBeNull());
});
test(‘menampilkan nama dan email pengguna setelah data selesai dimuat’, async () => {
// Arrange
fetch.mockResolvedValue(Promise.resolve({ok: true, json: () => Promise.resolve({ name: ‘John Doe’, email: ‘john.doe@example.com’ })}));
// Act
render(
// Assert
await waitFor(() => {
expect(screen.getByText(‘John Doe’)).toBeInTheDocument();
expect(screen.getByText(‘john.doe@example.com’)).toBeInTheDocument();
});
});
“`
Dalam pengujian ini, kita menggunakan jest.fn()
untuk melakukan mocking fungsi fetch
. Ini memungkinkan kita untuk mengontrol data yang dikembalikan oleh API dan untuk menguji berbagai skenario.
Kita menggunakan waitFor
untuk menunggu komponen selesai memuat data sebelum membuat pernyataan. Ini karena data diambil secara asinkron.
Kita menggunakan queryByText
untuk memverifikasi bahwa pesan “Loading…” dan pesan kesalahan tidak ditampilkan setelah data selesai dimuat atau jika data berhasil dimuat.
5. Pemecahan Masalah Umum
Berikut adalah beberapa masalah umum yang mungkin Anda temui saat memverifikasi ketidakadaan elemen:
5.1. Elemen Masih Hadir
Jika pengujian Anda gagal dan Anda yakin bahwa elemen seharusnya tidak ada, pastikan bahwa:
- Anda menggunakan metode kueri yang benar (
queryBy*
, bukangetBy*
). - Anda menggunakan pemilih yang benar.
- Kondisi yang seharusnya menyebabkan elemen menghilang benar-benar terpenuhi.
Anda dapat menggunakan screen.debug()
untuk memeriksa output yang dirender dan memastikan bahwa elemen tersebut benar-benar ada atau tidak.
5.2. Kesalahan dalam Query
Pastikan bahwa kueri Anda benar dan sesuai dengan elemen yang Anda cari. Perhatikan penggunaan `aria-label` atau `name` pada elemen form seperti input, karena Testing Library lebih menyarankan pencarian berdasarkan label pada elemen form dibandingkan berdasarkan `id`.
Contoh:
“`javascript
// Salah jika input memiliki label
const input = screen.queryByTestId(‘my-input’);
// Benar jika input memiliki label “Nama Depan”
const input = screen.queryByRole(‘textbox’, { name: ‘Nama Depan’ });
“`
5.3. Masalah Asinkron
Jika komponen Anda memuat data secara asinkron, Anda mungkin perlu menggunakan waitFor
untuk menunggu komponen selesai memuat data sebelum membuat pernyataan. Jika tidak, pengujian Anda mungkin akan selesai sebelum elemen menghilang.
Contoh:
“`javascript
import { render, screen, waitFor } from ‘@testing-library/react’;
import MyComponent from ‘./MyComponent’;
test(‘pesan loading tidak ditampilkan setelah data selesai dimuat’, async () => {
render(
await waitFor(() => expect(screen.queryByText(‘Loading…’)).toBeNull());
});
“`
6. Kesimpulan
Memverifikasi ketidakadaan elemen adalah bagian penting dari pengujian komponen React. Dengan menggunakan metode queryBy*
dan mengikuti praktik terbaik, Anda dapat menulis pengujian yang efektif dan mudah dipelihara yang memastikan bahwa aplikasi Anda berperilaku seperti yang diharapkan.
Ingatlah untuk selalu fokus pada pengujian perilaku komponen, bukan detail implementasinya. Gunakan pemilih yang stabil dan hindari pemilih yang rapuh. Gunakan userEvent
untuk mensimulasikan interaksi pengguna dan gunakan mocking dengan bijak.
Dengan mengikuti panduan ini, Anda akan dapat menulis pengujian yang lebih baik dan memastikan kualitas aplikasi React Anda.
“`