Tingkatkan Pemeliharaan Kode Dengan Pengujian Integrasi React

Diterbitkan: 2022-03-11

Tes 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.