Pengujian komponen adalah tempat yang baik untuk mulai mendemonstrasikan kode pengujian praktis. Pengujian komponen lebih substansial dibandingkan pengujian unit sederhana, tidak terlalu rumit dibandingkan pengujian menyeluruh, dan menunjukkan interaksi dengan DOM. Secara lebih filosofis, penggunaan React memudahkan developer web untuk menganggap situs atau aplikasi web terdiri dari komponen.
Jadi, menguji komponen individual, terlepas dari seberapa rumitnya, adalah cara yang baik untuk mulai berpikir untuk menguji aplikasi baru atau yang sudah ada.
Halaman ini memandu 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 jumlahnya bertambah. Pada kenyataannya, kode seperti itu sangat sedikit, dan kode pengujian yang tidak memiliki interaksi dapat memiliki nilai terbatas.
(Ini tidak dimaksudkan sebagai tutorial lengkap, dan bagian selanjutnya, Praktik pengujian otomatis, akan memandu pengujian situs sungguhan dengan kode contoh yang dapat Anda gunakan sebagai tutorial. Namun, halaman ini masih akan membahas beberapa contoh pengujian komponen praktis.)
Komponen yang sedang diuji
Kita akan menggunakan Vitest dan lingkungan JSDOM-nya untuk menguji komponen React. Hal ini memungkinkan kita menjalankan pengujian dengan cepat menggunakan Node pada command line sambil mengemulasi browser.
Komponen React yang 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
. Ini adalah 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 ini. Lebih penting lagi, kasus ini bisa tampak sulit untuk diuji secara
singkat. Bagian selanjutnya dari kursus ini akan membahas penulisan kode yang dapat diuji secara
mendetail.
Berikut hal-hal yang kami uji dalam contoh ini:
- Periksa apakah beberapa DOM yang benar telah 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 sungguhan dari jaringan, yang mungkin tidak stabil atau lambat dalam pengujian. - Kode ini mengimpor class lain,
UserRow
, yang mungkin tidak ingin kita uji secara implisit. - Class ini menggunakan
Context
yang secara khusus bukan 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. Untuk lebih jelasnya, contoh ini
tidak terlalu berguna! Namun, sebaiknya siapkan boilerplate dalam file pembanding
yang disebut UserList.test.tsx
(ingat, runner pengujian seperti Vitest,
secara default, akan 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 menegaskan bahwa saat komponen dirender, komponen tersebut akan berisi teks "Users".
Ini berfungsi meskipun komponen memiliki efek samping dalam mengirim fetch
ke
jaringan. fetch
masih berlangsung di akhir pengujian, tanpa
endpoint yang ditetapkan. Kami tidak dapat mengonfirmasi apakah informasi pengguna ditampilkan saat pengujian berakhir, setidaknya tanpa menunggu waktu tunggu.
Simulasi fetch()
Tiruan adalah tindakan mengganti fungsi atau class sebenarnya dengan sesuatu yang ada di bawah kendali Anda untuk suatu pengujian. Hal ini umum dilakukan di hampir semua jenis pengujian, kecuali untuk pengujian unit yang paling sederhana. Hal ini akan dibahas lebih lanjut dalam 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 dikenal. fetch
adalah global,
yang berarti kita tidak harus melakukan import
atau require
ke dalam kode.
Dalam vitest, Anda dapat membuat tiruan global dengan memanggil vi.stubGlobal
dengan objek
khusus yang ditampilkan oleh vi.fn()
—ini akan membuat tiruan yang dapat kita ubah nanti. Metode
ini akan dibahas secara lebih mendetail di bagian selanjutnya dalam kursus ini, tetapi
Anda dapat melihatnya dalam praktiknya di 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" dari 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 telah 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 "mengurungkan" pekerjaan, tiruan fetch
dapat memengaruhi pengujian lain, dan setiap permintaan akan direspons dengan tumpukan JSON yang ganjil.
Impor tiruan
Anda mungkin telah melihat bahwa komponen UserList
kita mengimpor komponen
yang disebut UserRow
. Meskipun kita belum menyertakan kodenya, Anda dapat melihat bahwa kode tersebut
merender nama pengguna: pemeriksaan pengujian sebelumnya untuk "Sam", dan yang tidak
dirender di dalam UserList
secara langsung, sehingga harus berasal dari UserRow
.
Namun, UserRow
mungkin merupakan komponen yang kompleks—produk ini dapat mengambil data pengguna
lebih lanjut, atau memiliki efek samping yang tidak relevan dengan pengujian kami. Menghapus
variasi tersebut akan membuat pengujian Anda lebih bermanfaat, terutama karena komponen yang ingin Anda
uji akan lebih kompleks dan lebih terkait dengan dependensinya.
Untungnya, Anda dapat menggunakan Vitest untuk meniru impor tertentu, meskipun pengujian Anda tidak menggunakannya secara langsung, sehingga setiap kode yang menggunakannya akan disediakan versi sederhana atau yang diketahui:
vi.mock('./UserRow.tsx', () => {
return {
UserRow(arg) {
return <>{arg.u.name}</>;
},
}
});
test('render', async () => {
// ...
});
Seperti meniru global fetch
, ini adalah alat yang canggih, tetapi mungkin tidak
berkelanjutan jika kode Anda memiliki banyak dependensi. Sekali lagi, perbaikan terbaik untuk itu
adalah menulis kode yang dapat diuji.
Klik dan berikan konteks
React, dan library lainnya seperti Lit,
memiliki konsep yang disebut Context
. Kode contoh ini menyertakan UserContext
yang
memanggil metode jika pengguna dipilih. Hal ini sering dilihat sebagai alternatif untuk
"pengeboran properti", dengan callback yang diteruskan ke UserList
secara langsung.
Harness pengujian yang kami tulis belum memberikan UserContext
. Dengan menambahkan tindakan klik
ke pengujian React tanpa tindakan ini, hal ini akan, paling buruk, akan menyebabkan error pada pengujian, atau
hal terbaiknya, jika instance default disediakan di tempat lain, 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();
Sebagai gantinya, saat merender komponen, Anda dapat memberikan Context
Anda sendiri. Contoh
ini menggunakan instance vi.fn()
, Vitest Mock Function, yang dapat digunakan
setelah fakta untuk memeriksa bahwa panggilan dilakukan, dan argumen yang digunakan
tersebut. Dalam kasus ini, kode ini berinteraksi dengan fetch
tiruan 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 yang sederhana tetapi efektif, yang dapat Anda gunakan untuk menghapus dependensi yang tidak relevan dari komponen inti yang Anda coba uji.
Ringkasan
Ini telah menjadi contoh cepat dan sederhana yang menunjukkan cara mem-build
pengujian komponen untuk menguji dan mengamankan komponen React yang sulit diuji,
yang berfokus pada memastikan bahwa komponen berinteraksi dengan
dependensinya dengan benar (global fetch
, subkomponen yang diimpor, dan Context
).
Menguji pemahaman Anda
Pendekatan apa yang digunakan untuk menguji komponen React?