Tingkatkan Pemeliharaan Kode Dengan Pengujian Integrasi React
Diterbitkan: 2022-03-11Tes integrasi adalah titik manis antara biaya dan nilai tes. Menulis tes integrasi untuk aplikasi React dengan bantuan react-testing-library alih-alih atau sebagai tambahan dari pengujian unit komponen dapat meningkatkan pemeliharaan kode tanpa mengganggu kecepatan pengembangan.
Jika Anda ingin memulai sebelum melanjutkan, Anda dapat melihat contoh bagaimana menggunakan react-testing-library untuk pengujian integrasi aplikasi React di sini.
Mengapa Berinvestasi dalam Pengujian Integrasi?
"Tes integrasi memberikan keseimbangan yang baik pada trade-off antara kepercayaan diri dan kecepatan/biaya. Inilah mengapa disarankan untuk menghabiskan sebagian besar (tidak semua, ingatlah) upaya Anda di sana."
– Kent C. Dodds dalam tes Tulis. Tidak terlalu banyak. Kebanyakan integrasi.
Ini adalah praktik umum untuk menulis pengujian unit untuk komponen React, sering kali menggunakan perpustakaan populer untuk menguji "enzim" React; khususnya, metode "dangkal". Pendekatan ini memungkinkan kami menguji komponen secara terpisah dari aplikasi lainnya. Namun, karena menulis aplikasi React adalah tentang menyusun komponen, pengujian unit saja tidak memastikan bahwa aplikasi tersebut bebas bug.
Misalnya, mengubah props komponen yang diterima dan memperbarui pengujian unit terkaitnya dapat mengakibatkan semua pengujian lulus sementara aplikasi mungkin masih rusak jika komponen lain tidak diperbarui sebagaimana mestinya.
Tes integrasi dapat membantu menjaga ketenangan pikiran saat membuat perubahan pada aplikasi React, karena memastikan bahwa komposisi komponen menghasilkan UX yang diinginkan.
Persyaratan untuk Tes Integrasi Aplikasi React
Berikut adalah beberapa hal yang ingin dilakukan pengembang Bereaksi saat menulis tes integrasi:
- Uji kasus penggunaan aplikasi dari sudut pandang pengguna. Pengguna mengakses informasi di halaman web dan berinteraksi dengan kontrol yang tersedia.
- Panggilan API tiruan untuk tidak bergantung pada ketersediaan dan status API untuk lulus/gagal tes.
- API browser tiruan (misalnya, penyimpanan lokal) karena tidak ada di lingkungan pengujian.
- Tegaskan pada status React DOM (DOM browser atau lingkungan seluler asli).
Sekarang, untuk beberapa hal yang harus kita hindari saat menulis tes integrasi aplikasi React:
- Detail implementasi pengujian. Perubahan implementasi seharusnya hanya merusak pengujian jika memang memperkenalkan bug.
- Terlalu banyak mengejek. Kami ingin menguji bagaimana semua bagian aplikasi bekerja bersama.
- Render dangkal. Kami ingin menguji komposisi semua komponen dalam aplikasi hingga komponen terkecil.
Mengapa Memilih React-testing-library?
Persyaratan yang disebutkan di atas membuat perpustakaan pengujian-reaksi menjadi pilihan yang tepat, karena prinsip panduan utamanya adalah memungkinkan komponen React untuk diuji dengan cara yang menyerupai bagaimana mereka digunakan oleh manusia yang sebenarnya.
Pustaka, bersama dengan pustaka pendamping opsionalnya, memungkinkan kita menulis pengujian yang berinteraksi dengan DOM dan menegaskan statusnya.
Contoh Pengaturan Aplikasi
Aplikasi yang akan kita tulis contoh pengujian integrasinya menerapkan skenario sederhana:
- Pengguna memasukkan nama pengguna GitHub.
- Aplikasi ini menampilkan daftar repositori publik yang terkait dengan nama pengguna yang dimasukkan.
Bagaimana fungsionalitas di atas diimplementasikan seharusnya tidak relevan dari perspektif pengujian integrasi. Namun, untuk tetap dekat dengan aplikasi dunia nyata, aplikasi mengikuti pola React yang umum, maka aplikasi:
- Adalah aplikasi satu halaman (SPA).
- Membuat permintaan API.
- Memiliki manajemen negara global.
- Mendukung internasionalisasi.
- Memanfaatkan pustaka komponen Bereaksi.
Kode sumber untuk implementasi aplikasi dapat ditemukan di sini.
Menulis Tes Integrasi
Menginstal dependensi
Dengan benang:
yarn add --dev jest @testing-library/react @testing-library/user-event jest-dom nock
Atau dengan npm:
npm i -D jest @testing-library/react @testing-library/user-event jest-dom nock
Membuat File Suite Tes Integrasi
Kami akan membuat file bernama viewGitHubRepositoriesByUsername.spec.js
file di ./test
folder aplikasi kami. Jest akan secara otomatis mengambilnya.
Mengimpor Dependensi dalam File Tes
import React from 'react'; // so that we can use JSX syntax import { render, cleanup, waitForElement } from '@testing-library/react'; // testing helpers import userEvent from '@testing-library/user-event' // testing helpers for imitating user events import 'jest-dom/extend-expect'; // to extend Jest's expect with DOM assertions import nock from 'nock'; // to mock github API import { FAKE_USERNAME_WITH_REPOS, FAKE_USERNAME_WITHOUT_REPOS, FAKE_BAD_USERNAME, REPOS_LIST } from './fixtures/github'; // test data to use in a mock API import './helpers/initTestLocalization'; // to configure i18n for tests import App from '../App'; // the app that we are going to test
Menyiapkan Test Suite
describe('view GitHub repositories by username', () => { beforeAll(() => { nock('https://api.github.com') .persist() .get(`/users/${FAKE_USERNAME_WITH_REPOS}/repos`) .query(true) .reply(200, REPOS_LIST); }); afterEach(cleanup); describe('when GitHub user has public repositories', () => { it('user can view the list of public repositories for entered GitHub username', async () => { // arrange // act // assert }); }); describe('when GitHub user has no public repositories', () => { it('user is presented with a message that there are no public repositories for entered GitHub username', async () => { // arrange // act // assert }); }); describe('when GitHub user does not exist', () => { it('user is presented with an error message', async () => { // arrange // act // assert }); }); });
Catatan:
- Sebelum semua pengujian, pura-pura GitHub API untuk mengembalikan daftar repositori saat dipanggil dengan nama pengguna tertentu.
- Setelah setiap pengujian, bersihkan pengujian React DOM sehingga setiap pengujian dimulai dari titik yang bersih.
-
describe
blok menentukan kasus penggunaan uji integrasi dan variasi aliran. - Variasi aliran yang kami uji adalah:
- Pengguna memasukkan nama pengguna yang valid yang memiliki repositori GitHub publik yang terkait.
- Pengguna memasukkan nama pengguna yang valid yang tidak memiliki repositori GitHub publik terkait.
- Pengguna memasukkan nama pengguna yang tidak ada di GitHub.
-
it
memblokir penggunaan panggilan balik async karena kasus penggunaan yang mereka uji memiliki langkah asinkron di dalamnya.
Menulis Tes Aliran Pertama
Pertama, aplikasi perlu dirender.
const { getByText, getByPlaceholderText, queryByText } = render(<App />);
Metode render
yang diimpor dari modul @testing-library/react
merender aplikasi dalam DOM React pengujian dan mengembalikan kueri DOM yang terikat ke wadah aplikasi yang dirender. Kueri ini digunakan untuk menemukan elemen DOM untuk berinteraksi dengan dan untuk menegaskan.
Sekarang, sebagai langkah pertama dari alur yang diuji, pengguna disajikan dengan bidang nama pengguna dan mengetikkan string nama pengguna ke dalamnya.
userEvent.type(getByPlaceholderText('userSelection.usernamePlaceholder'), FAKE_USERNAME_WITH_REPOS);
Helper userEvent
dari modul @testing-library/user-event
diimpor memiliki metode type
yang meniru perilaku pengguna saat mereka mengetik teks ke dalam bidang teks. Ia menerima dua parameter: elemen DOM yang menerima input dan string yang diketik pengguna.

Pengguna biasanya menemukan elemen DOM melalui teks yang terkait dengannya. Dalam hal input, itu adalah teks label atau teks placeholder. Metode kueri getByPlaceholderText
yang dikembalikan sebelumnya dari render
memungkinkan kita menemukan elemen DOM dengan teks placeholder.
Harap dicatat bahwa karena teks itu sendiri sering berubah, yang terbaik adalah tidak bergantung pada nilai pelokalan yang sebenarnya dan sebagai gantinya mengonfigurasi modul pelokalan untuk mengembalikan kunci item pelokalan sebagai nilainya.
Misalnya, ketika pelokalan "en-US" biasanya mengembalikan Enter GitHub username
sebagai nilai untuk kunci userSelection.usernamePlaceholder
, dalam pengujian, kami ingin mengembalikan userSelection.usernamePlaceholder
.
Saat pengguna mengetik teks ke dalam bidang, mereka akan melihat nilai bidang teks diperbarui.
expect(getByPlaceholderText('userSelection.usernamePlaceholder')).toHaveAttribute('value', FAKE_USERNAME_WITH_REPOS);
Selanjutnya dalam alur, pengguna mengklik tombol kirim dan berharap untuk melihat daftar repositori.
userEvent.click(getByText('userSelection.submitButtonText').closest('button')); getByText('repositories.header');
Metode userEvent.click
meniru pengguna mengklik elemen DOM, sementara kueri getByText
menemukan elemen DOM dengan teks yang dikandungnya. Pengubah closest
memastikan bahwa kita memilih elemen dari jenis yang tepat.
Catatan: Dalam pengujian integrasi, langkah-langkah sering kali berfungsi baik act
maupun assert
peran. Misalnya, kami menegaskan bahwa pengguna dapat mengklik tombol dengan mengkliknya.
Pada langkah sebelumnya, kami menegaskan bahwa pengguna melihat bagian daftar repositori aplikasi. Sekarang, kita perlu menegaskan bahwa karena mengambil daftar repositori dari GitHub mungkin memakan waktu, pengguna melihat indikasi bahwa pengambilan sedang berlangsung. Kami juga ingin memastikan bahwa aplikasi tidak memberi tahu pengguna bahwa tidak ada repositori yang terkait dengan nama pengguna yang dimasukkan saat daftar repositori masih diambil.
getByText('repositories.loadingText'); expect(queryByText('repositories.empty')).toBeNull();
Perhatikan bahwa awalan kueri getBy
digunakan untuk menegaskan bahwa elemen DOM dapat ditemukan dan awalan kueri queryBy
berguna untuk pernyataan sebaliknya. Juga, queryBy
tidak mengembalikan kesalahan jika tidak ada elemen yang ditemukan.
Selanjutnya, kami ingin memastikan bahwa, pada akhirnya, aplikasi selesai mengambil repositori dan menampilkannya kepada pengguna.
await waitForElement(() => REPOS_LIST.reduce((elementsToWaitFor, repository) => { elementsToWaitFor.push(getByText(repository.name)); elementsToWaitFor.push(getByText(repository.description)); return elementsToWaitFor; }, []));
Metode asinkron waitForElement
digunakan untuk menunggu pembaruan DOM yang akan membuat pernyataan yang diberikan sebagai parameter metode benar. Dalam hal ini, kami menegaskan bahwa aplikasi menampilkan nama dan deskripsi untuk setiap repositori yang dikembalikan oleh API GitHub tiruan.
Terakhir, aplikasi seharusnya tidak lagi menampilkan indikator bahwa repositori sedang diambil dan seharusnya tidak menampilkan pesan kesalahan.
expect(queryByText('repositories.loadingText')).toBeNull(); expect(queryByText('repositories.error')).toBeNull();
Hasil tes integrasi React kami terlihat seperti ini:
it('user can view the list of public repositories for entered GitHub username', async () => { const { getByText, getByPlaceholderText, queryByText } = render(<App />); userEvent.type(getByPlaceholderText('userSelection.usernamePlaceholder'), FAKE_USERNAME_WITH_REPOS); expect(getByPlaceholderText('userSelection.usernamePlaceholder')).toHaveAttribute('value', FAKE_USERNAME_WITH_REPOS); userEvent.click(getByText('userSelection.submitButtonText').closest('button')); getByText('repositories.header'); getByText('repositories.loadingText'); expect(queryByText('repositories.empty')).toBeNull(); await waitForElement(() => REPOS_LIST.reduce((elementsToWaitFor, repository) => { elementsToWaitFor.push(getByText(repository.name)); elementsToWaitFor.push(getByText(repository.description)); return elementsToWaitFor; }, [])); expect(queryByText('repositories.loadingText')).toBeNull(); expect(queryByText('repositories.error')).toBeNull(); });
Tes Aliran Alternatif
Saat pengguna memasukkan nama pengguna GitHub tanpa repositori publik terkait, aplikasi akan menampilkan pesan yang sesuai.
describe('when GitHub user has no public repositories', () => { it('user is presented with a message that there are no public repositories for entered GitHub username', async () => { const { getByText, getByPlaceholderText, queryByText } = render(<App />); userEvent.type(getByPlaceholderText('userSelection.usernamePlaceholder'), FAKE_USERNAME_WITHOUT_REPOS); expect(getByPlaceholderText('userSelection.usernamePlaceholder')).toHaveAttribute('value', FAKE_USERNAME_WITHOUT_REPOS); userEvent.click(getByText('userSelection.submitButtonText').closest('button')); getByText('repositories.header'); getByText('repositories.loadingText'); expect(queryByText('repositories.empty')).toBeNull(); await waitForElement(() => getByText('repositories.empty')); expect(queryByText('repositories.error')).toBeNull(); }); });
Saat pengguna memasukkan nama pengguna GitHub yang tidak ada, aplikasi akan menampilkan pesan kesalahan.
describe('when GitHub user does not exist', () => { it('user is presented with an error message', async () => { const { getByText, getByPlaceholderText, queryByText } = render(<App />); userEvent.type(getByPlaceholderText('userSelection.usernamePlaceholder'), FAKE_BAD_USERNAME); expect(getByPlaceholderText('userSelection.usernamePlaceholder')).toHaveAttribute('value', FAKE_BAD_USERNAME); userEvent.click(getByText('userSelection.submitButtonText').closest('button')); getByText('repositories.header'); getByText('repositories.loadingText'); expect(queryByText('repositories.empty')).toBeNull(); await waitForElement(() => getByText('repositories.error')); expect(queryByText('repositories.empty')).toBeNull(); }); });
Mengapa Integrasi Bereaksi Menguji Rock
Pengujian integrasi benar-benar menawarkan sweet spot untuk aplikasi React. Tes ini membantu menangkap bug dan menggunakan pendekatan TDD sementara, pada saat yang sama, mereka tidak memerlukan pemeliharaan saat implementasi berubah.
React-testing-library, yang ditampilkan dalam artikel ini, adalah alat yang hebat untuk menulis tes integrasi React, karena memungkinkan Anda untuk berinteraksi dengan aplikasi seperti yang dilakukan pengguna dan memvalidasi status dan perilaku aplikasi dari sudut pandang pengguna.
Mudah-mudahan, contoh yang diberikan di sini akan membantu Anda mulai menulis tes integrasi pada proyek React baru dan yang sudah ada. Kode contoh lengkap yang menyertakan implementasi aplikasi dapat ditemukan di GitHub saya.