Pengembangan React.js Berbasis Tes: Pengujian Unit React.js dengan Enzyme dan Jest

Diterbitkan: 2022-03-11

Setiap bagian dari kode yang tidak memiliki tes dikatakan sebagai kode warisan, menurut Michael Feathers. Oleh karena itu, salah satu cara terbaik untuk menghindari pembuatan kode lama adalah menggunakan pengembangan yang digerakkan oleh tes (TDD).

Meskipun ada banyak alat yang tersedia untuk pengujian unit JavaScript dan React.js, dalam posting ini, kita akan menggunakan Jest dan Enzyme untuk membuat komponen React.js dengan fungsionalitas dasar menggunakan TDD.

Mengapa Menggunakan TDD untuk Membuat Komponen React.js?

TDD membawa banyak manfaat untuk kode Anda—salah satu keuntungan dari cakupan pengujian tinggi adalah memungkinkan pemfaktoran ulang kode yang mudah sambil menjaga kode Anda tetap bersih dan fungsional.

Jika Anda telah membuat komponen React.js sebelumnya, Anda menyadari bahwa kode dapat berkembang sangat cepat. Itu diisi dengan banyak kondisi kompleks yang disebabkan oleh pernyataan yang terkait dengan perubahan status dan panggilan layanan.

Setiap komponen yang tidak memiliki unit test memiliki kode lama yang menjadi sulit untuk dipelihara. Kita bisa menambahkan tes unit setelah kita membuat kode produksi. Namun, kami mungkin menghadapi risiko mengabaikan beberapa skenario yang seharusnya diuji. Dengan membuat tes terlebih dahulu, kami memiliki peluang lebih tinggi untuk mencakup setiap skenario logika dalam komponen kami, yang akan memudahkan untuk refactor dan pemeliharaan.

Bagaimana Kami Menguji Unit Komponen React.js?

Ada banyak strategi yang bisa kita gunakan untuk menguji komponen React.js:

  • Kami dapat memverifikasi bahwa fungsi tertentu dalam props dipanggil ketika suatu peristiwa dikirim.
  • Kita juga bisa mendapatkan hasil dari fungsi render yang diberikan status komponen saat ini dan mencocokkannya dengan tata letak yang telah ditentukan sebelumnya.
  • Kami bahkan dapat memeriksa apakah jumlah anak komponen sesuai dengan jumlah yang diharapkan.

Untuk menggunakan strategi ini, kita akan menggunakan dua alat yang berguna untuk bekerja dengan pengujian di React.js: Jest dan Enzyme.

Menggunakan Jest untuk Membuat Tes Unit

Jest adalah kerangka uji sumber terbuka yang dibuat oleh Facebook yang memiliki integrasi hebat dengan React.js. Ini termasuk alat baris perintah untuk eksekusi pengujian yang mirip dengan apa yang ditawarkan Jasmine dan Mocha. Ini juga memungkinkan kita untuk membuat fungsi tiruan dengan konfigurasi hampir nol dan menyediakan seperangkat pencocokan yang sangat bagus yang membuat pernyataan lebih mudah dibaca.

Selain itu, ia menawarkan fitur yang sangat bagus yang disebut "pengujian snapshot", yang membantu kami memeriksa dan memverifikasi hasil rendering komponen. Kami akan menggunakan pengujian snapshot untuk menangkap pohon komponen dan menyimpannya ke dalam file yang dapat kami gunakan untuk membandingkannya dengan pohon rendering (atau apa pun yang kami berikan ke fungsi expect sebagai argumen pertama.)

Menggunakan Enzim untuk Memasang Komponen React.js

Enzim menyediakan mekanisme untuk memasang dan melintasi pohon komponen React.js. Ini akan membantu kami mendapatkan akses ke properti dan statusnya sendiri serta properti turunannya untuk menjalankan pernyataan kami.

Enzim menawarkan dua fungsi dasar untuk pemasangan komponen: shallow dan mount . Fungsi shallow memuat dalam memori hanya komponen root sedangkan mount memuat pohon DOM penuh.

Kami akan menggabungkan Enzyme dan Jest untuk memasang komponen React.js dan menjalankan pernyataan di atasnya.

Langkah-langkah TDD untuk membuat komponen reaksi

Menyiapkan Lingkungan Kita

Anda dapat melihat repo ini, yang memiliki konfigurasi dasar untuk menjalankan contoh ini.

Kami menggunakan versi berikut:

 { "react": "16.0.0", "enzyme": "^2.9.1", "jest": "^21.2.1", "jest-cli": "^21.2.1", "babel-jest": "^21.2.0" }

Membuat Komponen React.js Menggunakan TDD

Langkah pertama adalah membuat tes yang gagal yang akan mencoba merender Komponen React.js menggunakan fungsi dangkal enzim.

 // MyComponent.test.js import React from 'react'; import { shallow } from 'enzyme'; import MyComponent from './MyComponent'; describe("MyComponent", () => { it("should render my component", () => { const wrapper = shallow(<MyComponent />); }); });

Setelah menjalankan tes, kami mendapatkan kesalahan berikut:

 ReferenceError: MyComponent is not defined.

Kami kemudian membuat komponen yang menyediakan sintaks dasar untuk membuat tes lulus.

 // MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div />; } }

Pada langkah berikutnya, kami akan memastikan komponen kami merender tata letak UI yang telah ditentukan sebelumnya menggunakan fungsi toMatchSnapshot dari Jest.

Setelah memanggil metode ini, Jest secara otomatis membuat file snapshot bernama [testFileName].snap , yang ditambahkan ke folder __snapshots__ .

File ini mewakili tata letak UI yang kami harapkan dari rendering komponen kami.

Namun, mengingat bahwa kita mencoba melakukan TDD murni , kita harus membuat file ini terlebih dahulu dan kemudian memanggil fungsi toMatchSnapshot untuk membuat pengujian gagal.

Ini mungkin terdengar sedikit membingungkan, mengingat kita tidak tahu format mana yang digunakan Jest untuk merepresentasikan tata letak ini.

Anda mungkin tergoda untuk menjalankan fungsi toMatchSnapshot terlebih dahulu dan melihat hasilnya di file snapshot, dan itu adalah opsi yang valid. Namun, jika kita benar-benar ingin menggunakan TDD murni , kita perlu mempelajari bagaimana file snapshot terstruktur.

File snapshot berisi tata letak yang cocok dengan nama pengujian. Ini berarti bahwa jika pengujian kami memiliki bentuk ini:

 desc("ComponentA" () => { it("should do something", () => { … } });

Kita harus menentukan ini di bagian ekspor: Component A should do something 1 .

Anda dapat membaca lebih lanjut tentang pengujian snapshot di sini.

Jadi, pertama kita buat file MyComponent.test.js.snap .

 //__snapshots__/MyComponent.test.js.snap exports[`MyComponent should render initial layout 1`] = ` Array [ <div> <input type="text" /> </div>, ] `;

Kemudian, kami membuat unit test yang akan memeriksa apakah snapshot cocok dengan elemen anak komponen.

 // MyComponent.test.js ... it("should render initial layout", () => { // when const component = shallow(<MyComponent />); // then expect(component.getElements()).toMatchSnapshot(); }); ...

Kita dapat mempertimbangkan components.getElements sebagai hasil dari metode render.

Kami meneruskan elemen-elemen ini ke metode expect untuk menjalankan verifikasi terhadap file snapshot.

Setelah menjalankan tes, kami mendapatkan kesalahan berikut:

 Received value does not match stored snapshot 1. Expected: - Array [ <div> <input type="text” /> </div>, ] Actual: + Array []

Jest memberi tahu kami bahwa hasil dari component.getElements tidak cocok dengan snapshot. Jadi, kami membuat tes ini lulus dengan menambahkan elemen input di MyComponent .

 // MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div><input type="text" /></div>; } }

Langkah selanjutnya adalah menambahkan fungsionalitas ke input dengan menjalankan fungsi ketika nilainya berubah. Kami melakukan ini dengan menentukan fungsi di prop onChange .

Pertama-tama kita perlu mengubah snapshot untuk membuat tes gagal.

 //__snapshots__/MyComponent.test.js.snap exports[`MyComponent should render initial layout 1`] = ` Array [ <div> <input onChange={[Function]} type="text" /> </div>, ] `;

Kelemahan dari memodifikasi snapshot terlebih dahulu adalah bahwa urutan props (atau atribut) itu penting.

Jest akan mengurutkan props yang diterima di fungsi expect secara alfabetis sebelum memverifikasinya dengan snapshot. Jadi, kita harus menentukannya dalam urutan itu.

Setelah menjalankan tes, kami mendapatkan kesalahan berikut:

 Received value does not match stored snapshot 1. Expected: - Array [ <div> onChange={[Function]} <input type="text”/> </div>, ] Actual: + Array [ <div> <input type=”text” /> </div>, ]

Untuk membuat tes ini lulus, kita cukup memberikan fungsi kosong ke onChange .

 // MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div><input onChange={() => {}} type="text" /></div>; } }

Kemudian, kami memastikan bahwa status komponen berubah setelah event onChange dikirim.

Untuk melakukan ini, kami membuat pengujian unit baru yang akan memanggil fungsi onChange di input dengan melewatkan peristiwa untuk meniru peristiwa nyata di UI.

Kemudian, kami memverifikasi bahwa status komponen berisi kunci bernama input .

 // MyComponent.test.js ... it("should create an entry in component state", () => { // given const component = shallow(<MyComponent />); const form = component.find('input'); // when form.props().onChange({target: { name: 'myName', value: 'myValue' }}); // then expect(component.state('input')).toBeDefined(); });

Kami sekarang mendapatkan kesalahan berikut.

 Expected value to be defined, instead received undefined

Ini menunjukkan bahwa komponen tidak memiliki properti dalam status yang disebut input .

Kami membuat tes lulus dengan menyetel entri ini dalam status komponen.

 // MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div><input onChange={(event) => {this.setState({input: ''})}} type="text" /></div>; } }

Kemudian, kita perlu memastikan nilai diatur dalam entri status baru. Nilai ini akan kita dapatkan dari acara tersebut.

Jadi, mari buat tes yang memastikan status berisi nilai ini.

 // MyComponent.test.js ... it("should create an entry in component state with the event value", () => { // given const component = shallow(<MyComponent />); const form = component.find('input'); // when form.props().onChange({target: { name: 'myName', value: 'myValue' }}); // then expect(component.state('input')).toEqual('myValue'); }); ~~~ Not surprisingly, we get the following error. ~~ Expected value to equal: "myValue" Received: ""

Kami akhirnya membuat tes ini lulus dengan mendapatkan nilai dari acara dan menetapkannya sebagai nilai input.

 // MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div><input onChange={(event) => { this.setState({input: event.target.value})}} type="text" /></div>; } }

Setelah memastikan semua tes lulus, kami dapat memfaktorkan ulang kode kami.

Kita dapat mengekstrak fungsi yang diteruskan di prop onChange ke fungsi baru yang disebut updateState .

 // MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { updateState(event) { this.setState({ input: event.target.value }); } render() { return <div><input onChange={this.updateState.bind(this)} type="text" /></div>; } }

Kami sekarang memiliki komponen React.js sederhana yang dibuat menggunakan TDD.

Ringkasan

Dalam contoh ini, kami mencoba menggunakan TDD murni dengan mengikuti setiap langkah menulis kode sesedikit mungkin untuk gagal dan lulus tes.

Beberapa langkah mungkin tampak tidak perlu dan kita mungkin tergoda untuk melewatkannya. Namun, setiap kali kami melewati langkah apa pun, kami akan menggunakan versi TDD yang kurang murni .

Menggunakan proses TDD yang tidak terlalu ketat juga valid dan dapat bekerja dengan baik.

Rekomendasi saya untuk Anda adalah untuk menghindari melewatkan langkah apa pun dan jangan merasa buruk jika Anda merasa kesulitan. TDD adalah teknik yang tidak mudah untuk dikuasai, tetapi pasti layak untuk dilakukan.

Jika Anda tertarik untuk mempelajari lebih lanjut tentang TDD dan pengembangan berbasis perilaku (BDD) terkait, baca Bos Anda Tidak Akan Menghargai TDD oleh sesama Toptaler Ryan Wilcox.