Pengujian komponen adalah tempat yang baik untuk mulai mendemonstrasikan kode pengujian praktis. Pengujian komponen lebih substansial daripada pengujian unit sederhana, tidak begitu kompleks dibandingkan pengujian menyeluruh, dan mendemonstrasikan interaksi dengan DOM. Secara filosofis, penggunaan React telah mempermudah developer web untuk menganggap situs atau aplikasi web terdiri dari komponen.
Jadi, menguji setiap komponen, terlepas dari seberapa kompleksnya, adalah cara yang baik untuk mulai memikirkan pengujian aplikasi baru atau yang sudah ada.
Halaman ini membahas pengujian komponen kecil dengan dependensi eksternal yang kompleks. Sangat mudah untuk menguji komponen yang tidak berinteraksi dengan kode lain, seperti dengan mengklik tombol dan mengonfirmasi bahwa angka meningkat. Pada kenyataannya, sangat sedikit kode yang seperti itu, dan pengujian kode yang tidak memiliki interaksi dapat memiliki nilai terbatas.
Komponen yang sedang diuji
Kita menggunakan Vitest dan lingkungan JSDOM-nya untuk menguji komponen React. Hal ini memungkinkan kita menjalankan pengujian dengan cepat menggunakan Node di command line saat mengemulasi browser.
Komponen React bernama UserList
ini mengambil daftar pengguna dari jaringan
dan memungkinkan Anda memilih salah satunya. Daftar pengguna diperoleh menggunakan
fetch
di dalam useEffect
, dan pengendali pemilihan diteruskan oleh
Context
. Berikut kodenya:
import React, { useEffect, useState, useContext } from 'react';
import { UserContext } from './UserContext.tsx';
import { UserRow } from './UserRow.tsx';
export function UserList({ count = 4 }: { count?: number }) {
const [users, setUsers] = useState<any[]>([]);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users?_limit=' + count)
.then((response) => response.json())
.then((json) => setUsers(json));
}, [count]);
const c = useContext(UserContext);
return (
<div>
<h2>Users</h2>
<ul>
{users.map((u) => (
<li key={u.id}>
<button onClick={() => c.userChosen(u.id)}>Choose</button>{' '}
<UserRow u={u} />
</li>
))}
</ul>
</div>
);
}
Contoh ini tidak menunjukkan praktik terbaik React (misalnya, menggunakan
fetch
di dalam useEffect
), tetapi codebase Anda kemungkinan berisi banyak kasus
seperti itu. Lebih jauh lagi, kasus ini mungkin tampak sulit diuji pada
pandangan pertama. Bagian mendatang dari kursus ini akan membahas penulisan kode yang dapat diuji secara
mendetail.
Berikut adalah hal-hal yang kami uji dalam contoh ini:
- Pastikan beberapa DOM yang benar dibuat sebagai respons terhadap data dari jaringan.
- Pastikan bahwa mengklik pengguna akan memicu callback.
Setiap komponen berbeda. Apa yang membuat pengujian ini menarik?
- Class ini menggunakan
fetch
global untuk meminta data dunia nyata dari jaringan, yang mungkin tidak stabil atau lambat saat diuji. - Class ini mengimpor class lain,
UserRow
, yang mungkin tidak ingin kita uji secara implisit. - Ini menggunakan
Context
yang tidak secara khusus merupakan bagian dari kode yang sedang diuji, dan biasanya disediakan oleh komponen induk.
Tulis pengujian cepat untuk memulai
Kita dapat dengan cepat menguji sesuatu yang sangat mendasar tentang komponen ini. Agar jelas, contoh ini tidak terlalu berguna. Namun, sebaiknya siapkan boilerplate dalam file peer
yang disebut UserList.test.tsx
(ingat, runner pengujian seperti Vitest, secara
default, menjalankan file yang diakhiri dengan .test.js
atau yang serupa, termasuk .tsx
):
import { vi, test, assert, afterAll } from 'vitest';
import { render } from '@testing-library/react';
import { UserList } from './UserList.tsx';
import React, { ContextType } from 'react';
test('render', async () => {
const c = render(<UserList />);
const headingNode = await c.findAllByText(/Users);
assert.isNotNull(headingNode);
});
Pengujian ini menyatakan bahwa saat komponen dirender, komponen tersebut berisi teks "Pengguna".
Metode ini berfungsi meskipun komponen memiliki efek samping dari pengiriman fetch
ke
jaringan. fetch
masih berlangsung di akhir pengujian, tanpa
endpoint yang ditetapkan. Kami tidak dapat mengonfirmasi bahwa setiap informasi pengguna ditampilkan saat pengujian berakhir, setidaknya tanpa menunggu waktu tunggu.
Simulasi fetch()
Tiruan adalah tindakan mengganti fungsi atau class sebenarnya dengan sesuatu yang berada di bawah kontrol Anda untuk pengujian. Ini adalah praktik umum di hampir semua jenis pengujian, kecuali untuk pengujian unit yang paling sederhana. Hal ini dibahas lebih lanjut di Pernyataan dan primitif lainnya.
Anda dapat meniru fetch()
untuk pengujian agar selesai dengan cepat dan menampilkan data yang Anda harapkan, dan bukan data "dunia nyata" atau data yang tidak diketahui. fetch
bersifat global,
yang berarti kita tidak harus import
atau require
-nya ke dalam kode.
Contohnya, Anda dapat membuat tiruan global dengan memanggil vi.stubGlobal
menggunakan objek
khusus yang ditampilkan oleh vi.fn()
—ini akan mem-build tiruan yang dapat diubah nanti. Metode ini dibahas secara lebih mendetail di bagian selanjutnya dari kursus ini, tetapi Anda dapat melihatnya dalam praktiknya pada kode berikut:
test('render', async () => {
const fetchMock = vi.fn();
fetchMock.mockReturnValue(
Promise.resolve({
json: () => Promise.resolve([{ name: 'Sam', id: 'sam' }]),
}),
);
vi.stubGlobal('fetch', fetchMock);
const c = render(<UserList />);
const headingNode = await c.queryByText(/Users);
assert.isNotNull(headingNode);
await waitFor(async () => {
const samNode = await c.queryByText(/Sam);
assert.isNotNull(samNode);
});
});
afterAll(() => {
vi.unstubAllGlobals();
});
Kode ini menambahkan tiruan, menjelaskan versi palsu pengambilan jaringan
Response
, lalu menunggunya muncul. Jika teks tidak muncul—Anda dapat memeriksanya dengan mengubah kueri di queryByText
menjadi nama baru—pengujian akan gagal.
Contoh ini menggunakan helper tiruan bawaan Vitest, tetapi framework pengujian
lainnya memiliki pendekatan yang serupa dengan tiruan. Vitest bersifat unik karena Anda harus
memanggil vi.unstubAllGlobals()
setelah semua pengujian, atau menetapkan opsi global
yang setara. Tanpa "membatalkan" pekerjaan kita, mock fetch
dapat memengaruhi pengujian lain, dan setiap permintaan akan direspons dengan tumpukan JSON yang aneh.
Impor tiruan
Anda mungkin telah memperhatikan bahwa komponen UserList
itu sendiri mengimpor komponen
yang disebut UserRow
. Meskipun kami belum menyertakan kodenya, Anda dapat melihat bahwa kode tersebut
merender nama pengguna: pengujian sebelumnya memeriksa "Sam", dan nama tersebut tidak
dirender di dalam UserList
secara langsung, sehingga harus berasal dari UserRow
.
Namun, UserRow
itu sendiri mungkin merupakan komponen yang kompleks—komponen ini mungkin mengambil data pengguna
lebih lanjut, atau memiliki efek samping yang tidak relevan dengan pengujian kita. Menghapus variabilitas
tersebut akan membuat pengujian Anda lebih bermanfaat, terutama saat komponen yang
ingin Anda uji menjadi lebih kompleks dan lebih terkait dengan dependensinya.
Untungnya, Anda dapat menggunakan Vitest untuk membuat tiruan impor tertentu, meskipun pengujian Anda tidak menggunakannya secara langsung, sehingga kode apa pun yang menggunakannya akan diberikan dengan versi sederhana atau yang diketahui:
vi.mock('./UserRow.tsx', () => {
return {
UserRow(arg) {
return <>{arg.u.name}</>;
},
}
});
test('render', async () => {
// ...
});
Seperti mengejek fetch
global, ini adalah alat yang canggih, tetapi dapat menjadi
tidak berkelanjutan jika kode Anda memiliki banyak dependensi. Sekali lagi, perbaikan terbaiknya adalah
menulis kode yang dapat diuji.
Klik dan berikan konteks
React, dan library lainnya seperti Lit,
memiliki konsep yang disebut Context
. Kode contoh menyertakan UserContext
, yang
memanggil metode jika pengguna dipilih. Hal ini sering kali dilihat sebagai alternatif untuk
"prop drilling", tempat callback diteruskan langsung ke UserList
.
Harness pengujian kami belum menyediakan UserContext
. Dengan menambahkan tindakan klik ke pengujian React tanpa tindakan tersebut, hal ini dapat menyebabkan error pada pengujian. Paling
baik, jika instance default disediakan di tempat lain, hal ini dapat menyebabkan beberapa perilaku
di luar kendali kita (serupa dengan UserRow
yang tidak diketahui di atas).
const c = render(<UserList />);
const chooseButton = await c.getByText(/Choose);
chooseButton.click();
Sebaliknya, saat merender komponen, Anda dapat menyediakan Context
Anda sendiri. Contoh
ini menggunakan instance vi.fn()
, Fungsi Tiruan Vitest, yang dapat digunakan
untuk memeriksa apakah panggilan telah dilakukan dan argumen apa yang digunakannya.
Dalam kasus kami, ini berinteraksi dengan fetch
yang di-mock dalam contoh
sebelumnya, dan pengujian dapat mengonfirmasi bahwa ID yang diteruskan adalah sam
:
const userChosenFn = vi.fn();
const ucForTest: ContextType<typeof UserContext> = { userChosen: userChosenFn as any };
const c = render(
<UserContext.Provider value={ucForTest}>
<UserList />
</UserContext.Provider>,
);
const chooseButton = await c.getByText(/Choose);
chooseButton.click();
assert.deepStrictEqual(userChosenFn.mock.calls, [['sam']]);
Ini adalah pola sederhana tetapi efektif yang memungkinkan Anda menghapus dependensi yang tidak relevan dari komponen inti yang Anda coba uji.
Ringkasan
Contoh ini menunjukkan cara mem-build pengujian komponen untuk menguji dan melindungi
komponen React yang sulit diuji. Pengujian ini berfokus pada memastikan bahwa
komponen berinteraksi dengan benar dengan dependensinya: fetch
global, subkomponen yang diimpor, dan Context
.
Memeriksa pemahaman Anda
Pendekatan apa yang digunakan untuk menguji komponen React?