Bagaimana Komponen React Membuat Pengujian UI Mudah

Diterbitkan: 2022-03-11

Menguji back-end itu mudah. Anda mengambil bahasa pilihan Anda, memasangkannya dengan kerangka kerja favorit Anda, menulis beberapa tes, dan tekan "jalankan." Konsol Anda mengatakan “Yay! Berhasil!" Layanan integrasi berkelanjutan Anda menjalankan pengujian Anda pada setiap dorongan, hidup ini luar biasa.

Tentu, test-driven development (TDD) memang aneh pada awalnya, tetapi lingkungan yang dapat diprediksi, beberapa test runner, alat uji yang dimasukkan ke dalam kerangka kerja, dan dukungan integrasi berkelanjutan, membuat hidup menjadi mudah. Lima tahun lalu saya pikir tes adalah solusi untuk setiap masalah yang pernah saya alami.

Kemudian Backbone menjadi besar.

Kita semua beralih ke MVC front-end. Backend kami yang dapat diuji menjadi server basis data yang dimuliakan. Kode kami yang paling rumit dipindahkan ke browser. Dan aplikasi kami tidak lagi dapat diuji dalam praktiknya.

Itu karena menguji kode front-end dan komponen UI agak sulit.

Tidak terlalu buruk jika yang kita inginkan hanyalah memeriksa apakah model kita berperilaku baik. Atau, pemanggilan suatu fungsi akan mengubah nilai yang benar. Yang perlu kita lakukan untuk pengujian unit React adalah:

  • Tulis modul terisolasi yang terbentuk dengan baik.
  • Gunakan tes Jasmine atau Mocha (atau apa pun) untuk menjalankan fungsi.
  • Gunakan pelari uji, seperti Karma atau Chutzpah.

Itu dia. Kode kami diuji unit.

Dulu menjalankan tes front-end adalah bagian yang sulit. Setiap kerangka kerja memiliki idenya sendiri dan dalam banyak kasus Anda berakhir dengan jendela browser yang akan Anda segarkan secara manual setiap kali Anda ingin menjalankan tes. Tentu saja, Anda akan selalu lupa. Setidaknya, aku tahu aku melakukannya.

Pada 2012, Vojta Jina merilis pelari Karma (disebut Testacular pada saat itu). Dengan Karma, pengujian front-end menjadi warga penuh dari rantai alat. Tes React kami berjalan di terminal atau pada server integrasi berkelanjutan, mereka berjalan kembali sendiri ketika kami mengubah file, dan kami bahkan dapat menguji kode kami di beberapa browser pada saat yang bersamaan.

Apa lagi yang bisa kita harapkan? Nah, untuk benar- benar menguji kode front-end kita.

Pengujian Front-End Membutuhkan Lebih Dari Sekedar Pengujian Unit

Pengujian unit sangat bagus: ini adalah cara terbaik untuk melihat apakah suatu algoritme melakukan hal yang benar setiap saat, atau untuk memeriksa logika validasi input kami, atau transformasi data, atau operasi terisolasi lainnya. Pengujian unit sangat cocok untuk fundamental.

Tetapi kode front-end bukan tentang memanipulasi data. Ini tentang peristiwa pengguna dan menampilkan tampilan yang tepat pada waktu yang tepat. Front-end adalah tentang pengguna.

Inilah yang ingin kami lakukan:

  • Uji acara pengguna Bereaksi
  • Uji respons terhadap peristiwa tersebut
  • Pastikan hal yang tepat ditampilkan pada waktu yang tepat
  • Jalankan tes di banyak browser
  • Jalankan kembali tes pada perubahan file
  • Bekerja dengan sistem integrasi berkelanjutan seperti Travis

Dalam sepuluh tahun saya melakukan ini, saya belum menemukan cara yang layak untuk menguji interaksi pengguna dan melihat rendering sampai saya mulai mencoba React.

React Unit Testing: Komponen UI

Bereaksi adalah cara termudah untuk mencapai tujuan ini. Sebagian, karena cara ini memaksa kita untuk merancang aplikasi menggunakan pola yang dapat diuji, sebagian, karena ada utilitas pengujian React yang fantastis.

Jika Anda belum pernah menggunakan React sebelumnya, Anda harus membaca buku saya React+d3.js . Ini ditujukan untuk visualisasi, tetapi saya diberitahu bahwa ini adalah "intro ringan yang luar biasa" untuk React.

React memaksa kita untuk membangun semuanya sebagai "komponen." Anda dapat menganggap komponen React sebagai widget, atau sebagai potongan HTML dengan beberapa logika. Mereka mengikuti banyak prinsip terbaik dari pemrograman fungsional, kecuali mereka adalah objek.

Misalnya, dengan set parameter yang sama, komponen React akan selalu menghasilkan output yang sama. Tidak peduli berapa kali itu dirender, tidak peduli siapa yang merendernya, di mana pun kita menempatkan outputnya. Selalu sama. Akibatnya, kita tidak perlu melakukan scaffolding yang rumit untuk menguji komponen React. Mereka hanya peduli dengan properti mereka, tidak diperlukan pelacakan variabel global dan objek konfigurasi.

Kami mencapai ini sebagian besar dengan menghindari negara. Anda akan menyebutnya transparansi referensial dalam pemrograman fungsional. Saya tidak berpikir ada nama khusus untuk ini di Bereaksi, tetapi dokumen resmi merekomendasikan untuk menghindari penggunaan status sebanyak mungkin.

Ketika datang untuk menguji interaksi pengguna, React telah kami bahas dengan event yang terikat pada fungsi callback. Sangat mudah untuk menyiapkan mata-mata uji dan memastikan bahwa peristiwa klik memanggil fungsi yang tepat. Dan karena komponen React merender sendiri, kita bisa memicu event klik dan memeriksa HTML untuk perubahan. Ini berfungsi karena komponen React hanya peduli dengan dirinya sendiri. Sebuah klik di sini tidak mengubah hal-hal di sana . Kita tidak akan pernah harus berurusan dengan kumpulan event handler, hanya pemanggilan fungsi yang terdefinisi dengan baik.

Oh, dan karena React itu ajaib, kita tidak perlu khawatir tentang DOM. React menggunakan apa yang disebut DOM virtual untuk merender komponen ke dalam variabel JavaScript. Dan referensi ke DOM virtual adalah semua yang kita butuhkan untuk menguji komponen React, sungguh.

Ini cukup manis.

TestUtils React

React hadir dengan rangkaian TestUtils . Bahkan ada test runner yang direkomendasikan bernama Jest, tapi saya tidak menyukainya. Saya akan menjelaskan mengapa dalam sedikit. Pertama, TestUtils .

Kami mendapatkannya dengan melakukan sesuatu seperti require('react/addons').addons.TestUtils . Ini adalah titik masuk kami untuk menguji interaksi pengguna dan memeriksa hasilnya.

React TestUtils memungkinkan kita merender komponen React dengan meletakkan DOM-nya dalam variabel, alih-alih menyisipkannya ke dalam halaman. Misalnya, untuk merender komponen React, kita akan melakukan sesuatu seperti ini:

 var component = TestUtils.renderIntoDocument( <MyComponent /> );

Kemudian kita dapat menggunakan TestUtils untuk memeriksa apakah semua anak telah dirender. Sesuatu seperti ini:

 var h1 = TestUtils.findRenderedDOMComponentWithTag( component, 'h1' );

findRenderedDOMComponentWithTag akan melakukan apa yang terdengar seperti: menelusuri anak-anak, menemukan komponen yang kita cari, dan mengembalikannya. Nilai yang dikembalikan akan berperilaku seperti komponen React.

Kami kemudian dapat menggunakan getDOMNode() untuk mengakses elemen DOM mentah dan menguji nilainya. Untuk memeriksa bahwa tag h1 di komponen mengatakan “A title” , kami akan menulis ini:

 expect(h1.getDOMNode().textContent) .toEqual("A title");

Jika digabungkan, tes lengkapnya akan terlihat seperti ini:

 it("renders an h1", function () { var component = TestUtils.renderIntoDocument( <MyComponent /> ); var h1 = TestUtils.findRenderedDOMComponentWithTag( component, 'h1' ); expect(h1.getDOMNode().textContent) .toEqual("A title"); });

Bagian kerennya adalah TestUtils memungkinkan kita memicu peristiwa pengguna juga. Untuk acara klik, kami akan menulis sesuatu seperti ini:

 var node = component .findRenderedDOMComponentWithTag('button') .getDOMNode(); TestUtils.Simulate.click(node);

Ini mensimulasikan klik dan memicu semua pendengar potensial, yang harus berupa metode komponen yang mengubah output, status, atau keduanya. Pendengar tersebut dapat memanggil fungsi pada komponen induk jika perlu.

Semua kasus mudah untuk diuji: Status yang diubah ada di component.state , kita dapat mengakses output dengan fungsi DOM normal, dan panggilan fungsi dengan mata-mata.

Mengapa Tidak Bercanda?

Dokumentasi resmi React merekomendasikan penggunaan https://facebook.github.io/jest/ sebagai test runner dan framework pengujian React. Jest dibangun di atas Jasmine dan menggunakan sintaks yang sama. Di atas semua yang Anda dapatkan dari Jasmine, Jest juga mengolok-olok semuanya kecuali komponen yang kami uji. Ini fantastis dalam teori, tetapi saya merasa itu menjengkelkan. Apa pun yang belum kami implementasikan, atau yang berasal dari bagian lain dari basis kode, hanyalah undefined . Meskipun ini baik-baik saja dalam banyak kasus, ini dapat menyebabkan bug yang gagal secara diam-diam.

Saya mengalami masalah saat menguji acara klik, misalnya. Tidak peduli apa yang saya coba, itu tidak akan memanggil pendengarnya. Kemudian saya menyadari bahwa fungsi tersebut diejek oleh Jest dan tidak pernah memberi tahu saya hal ini.

Tapi pelanggaran terburuk Jest, sejauh ini, dulu tidak memiliki mode menonton untuk secara otomatis menguji perubahan baru. Kita bisa menjalankannya sekali, mendapatkan hasil tes, dan hanya itu. (Saya suka menjalankan tes saya di latar belakang saat saya bekerja. Kalau tidak, saya lupa menjalankannya.) Saat ini ini tidak lagi menjadi masalah.

Oh, dan Jest tidak mendukung menjalankan tes React di banyak browser. Ini bukan masalah daripada sebelumnya, tetapi saya merasa ini adalah fitur penting untuk kesempatan langka itu, bug heisen hanya terjadi di versi Chrome tertentu…

Catatan editor: Sejak artikel ini pertama kali ditulis, Jest telah meningkat secara substansial. Anda dapat membaca tutorial terbaru kami, React Unit Testing Using Enzyme and Jest, dan putuskan sendiri apakah pengujian Jest sesuai dengan tugas saat ini.

Pengujian Bereaksi: Contoh Terintegrasi

Bagaimanapun, kita telah melihat bagaimana tes React front-end yang baik seharusnya bekerja secara teori. Mari kita terapkan dengan contoh singkat.

Kita akan memvisualisasikan berbagai cara menghasilkan angka acak menggunakan komponen scatterplot yang dibuat dengan React dan d3.js. Kode dan demonya juga ada di Github.

Kita akan menggunakan Karma sebagai test runner, Mocha sebagai framework testing, dan Webpack sebagai module loader.

Pengaturan

File sumber kami akan masuk ke <root>/src , dan kami akan melakukan pengujian di <root>/src/__tests__ . Idenya adalah bahwa kita dapat menempatkan beberapa direktori di dalam src , satu untuk setiap komponen utama, dan masing-masing dengan file pengujiannya sendiri. Membundel kode sumber dan file pengujian seperti ini memudahkan untuk menggunakan kembali komponen React dalam proyek yang berbeda.

Dengan struktur direktori di tempat, kita dapat menginstal dependensi seperti ini:

 $ npm install --save-dev react d3 webpack babel-loader karma karma-cli karma-mocha karma-webpack expect

Jika ada yang gagal dipasang, coba jalankan kembali bagian penginstalan itu. NPM terkadang gagal dengan cara yang hilang saat dijalankan kembali.

File package.json kita akan terlihat seperti ini setelah kita selesai:

 // package.json { "name": "react-testing-example", "description": "A sample project to investigate testing options with ReactJS", "scripts": { "test": "karma start" }, // ... "homepage": "https://github.com/Swizec/react-testing-example", "devDependencies": { "babel-core": "^5.2.17", "babel-loader": "^5.0.0", "d3": "^3.5.5", "expect": "^1.6.0", "jsx-loader": "^0.13.2", "karma": "^0.12.31", "karma-chrome-launcher": "^0.1.10", "karma-cli": "0.0.4", "karma-mocha": "^0.1.10", "karma-sourcemap-loader": "^0.3.4", "karma-webpack": "^1.5.1", "mocha": "^2.2.4", "react": "^0.13.3", "react-hot-loader": "^1.2.7", "react-tools": "^0.13.3", "webpack": "^1.9.4", "webpack-dev-server": "^1.8.2" } }

Setelah beberapa konfigurasi, kita akan dapat menjalankan tes dengan npm test atau karma start .

Menjalankan Tes

Konfigurasi

Tidak banyak konfigurasinya. Kita harus memastikan Webpack tahu cara menemukan kode kita, dan Karma tahu cara menjalankan tes.

Kami menempatkan dua baris JavaScript dalam file ./tests.webpack.js untuk membantu Karma dan Webpack bermain bersama:

 // tests.webpack.js var context = require.context('./src', true, /-test\.jsx?$/); context.keys().forEach(context);

Ini memberitahu Webpack untuk mempertimbangkan apa pun dengan akhiran -test untuk menjadi bagian dari rangkaian pengujian.

Mengonfigurasi Karma membutuhkan sedikit lebih banyak pekerjaan:

 // karma.conf.js var webpack = require('webpack'); module.exports = function (config) { config.set({ browsers: ['Chrome'], singleRun: true, frameworks: ['mocha'], files: [ 'tests.webpack.js' ], preprocessors: { 'tests.webpack.js': ['webpack'] }, reporters: ['dots'], webpack: { module: { loaders: [ {test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader'} ] }, watch: true }, webpackServer: { noInfo: true } }); };

Sebagian besar baris ini berasal dari konfigurasi Karma default. Kami menggunakan browsers untuk mengatakan bahwa pengujian harus dijalankan di Chrome, frameworks untuk menentukan kerangka pengujian mana yang kami gunakan, dan singleRun untuk membuat pengujian berjalan hanya sekali secara default. Anda dapat menjaga karma berjalan di latar belakang dengan karma start --no-single-run .

Ketiganya jelas. Hal-hal Webpack lebih menarik.

Karena Webpack menangani pohon ketergantungan kode kita, kita tidak perlu menentukan semua file kita dalam larik files . Kami hanya membutuhkan tests.webpack.js , yang kemudian membutuhkan semua file yang diperlukan.

Kami menggunakan pengaturan webpack untuk memberi tahu Webpack apa yang harus dilakukan. Dalam lingkungan normal, bagian ini akan masuk ke file webpack.config.js .

Kami juga memberi tahu Webpack untuk menggunakan babel-loader untuk JavaScript kami. Ini memberi kita semua fitur baru yang mewah dari ECMAScript2015 dan JSX React.

Dengan konfigurasi webpackServer , kami memberi tahu Webpack untuk tidak mencetak info debug apa pun. Itu hanya akan merusak hasil pengujian kami.

Komponen Bereaksi dan Tes

Dengan rangkaian pengujian yang berjalan, sisanya sederhana. Kita harus membuat komponen yang menerima larik koordinat acak dan membuat elemen <svg> dengan sekumpulan titik.

Mengikuti praktik terbaik pengujian React—yaitu praktik TDD standar—kita akan menulis pengujian terlebih dahulu, lalu komponen React yang sebenarnya. Mari kita mulai dengan file tes vanilla di src/__tests__/ :

 // ScatterPlot-test.jsx var React = require('react/addons'), TestUtils = React.addons.TestUtils, expect = require('expect'), ScatterPlot = require('../ScatterPlot.jsx'); var d3 = require('d3'); describe('ScatterPlot', function () { var normal = d3.random.normal(1, 1), mockData = d3.range(5).map(function () { return {x: normal(), y: normal()}; }); });

Pertama kita membutuhkan React, TestUtils, expect , library ekspektasi, dan kode yang kita uji. Kemudian kita membuat test suite baru dengan describe , dan membuat beberapa data acak.

Untuk pengujian pertama kita, pastikan ScatterPlot merender sebuah judul. Pengujian kami masuk ke dalam blok describe :

 // ScatterPlot-test.jsx it("renders an h1", function () { var scatterplot = TestUtils.renderIntoDocument( <ScatterPlot /> ); var h1 = TestUtils.findRenderedDOMComponentWithTag( scatterplot, 'h1' ); expect(h1.getDOMNode().textContent).toEqual("This is a random scatterplot"); });

Sebagian besar tes akan mengikuti pola yang sama:

  1. Memberikan.
  2. Temukan simpul tertentu.
  3. Periksa isinya.

Seperti yang telah kita lihat sebelumnya, renderIntoDocument merender komponen kita, findRenderedDOMComponentWithTag menemukan bagian spesifik yang kita uji, dan getDOMNode memberi kita akses DOM mentah.

Pada awalnya tes kami akan gagal. Untuk membuatnya lulus, kita harus menulis komponen yang membuat tag judul:

 var React = require('react/addons'); var d3 = require('d3'); var ScatterPlot = React.createClass({ render: function () { return ( <div> <h1>This is a random scatterplot</h1> </div> ); } }); module.exports = ScatterPlot;

Itu dia. Komponen ScatterPlot merender <div> dengan tag <h1> yang berisi teks yang diharapkan, dan pengujian kami akan lulus. Ya, ini lebih panjang dari sekedar HTML, tapi bersabarlah.

Gambar Burung Hantu lainnya

Anda dapat melihat sisa contoh kami di GitHub, seperti yang disebutkan di atas. Kami akan melewatkan penjelasan langkah demi langkah dalam artikel ini, tetapi proses umumnya sama seperti di atas. Namun, saya ingin menunjukkan kepada Anda tes yang lebih menarik. Tes yang memastikan semua titik data muncul di grafik:

 // ScatterPlot-test.jsx it("renders a circle for each datapoint", function () { var scatterplot = TestUtils.renderIntoDocument( <ScatterPlot data={mockData} /> ); var circles = TestUtils.scryRenderedDOMComponentsWithTag( scatterplot, 'circle' ); expect(circles.length).toEqual(5); });

Sama seperti sebelumnya. Render, temukan node, periksa hasilnya. Bagian yang menarik di sini adalah menggambar node DOM tersebut. Kami menambahkan beberapa keajaiban d3.js ke komponen ScatterPlot , seperti ini:

 // ScatterPlot.jsx componentWillMount: function () { this.yScale = d3.scale.linear(); this.xScale = d3.scale.linear(); this.update_d3(this.props); }, componentWillReceiveProps: function (newProps) { this.update_d3(newProps); }, update_d3: function (props) { this.yScale .domain([d3.min(props.data, function (d) { return dy; }), d3.max(props.data, function (d) { return dy; })]) .range([props.point_r, Number(props.height-props.point_r)]); this.xScale .domain([d3.min(props.data, function (d) { return dx; }), d3.max(props.data, function (d) { return dx; })]) .range([props.point_r, Number(props.width-props.point_r)]); }, ...

Kami menggunakan componentWillMount untuk menyiapkan skala d3 kosong untuk domain X dan Y , dan componentWillReceiveProps untuk memastikan mereka diperbarui saat ada perubahan. Kemudian update_d3 pastikan untuk mengatur domain dan range untuk kedua skala.

Kami akan menggunakan dua skala untuk menerjemahkan antara nilai acak dalam dataset kami dan posisi pada gambar. Sebagian besar generator acak mengembalikan angka dalam rentang [0,1] , yang terlalu kecil untuk dilihat sebagai piksel.

Kemudian kami menambahkan poin ke metode render komponen kami:

 // ScatterPlot.jsx render: function () { return ( <div> <h1>This is a random scatterplot</h1> <svg width={this.props.width} height={this.props.height}> {this.props.data.map(function (pos, i) { var key = "circle-"+i; return ( <circle key={key} cx={this.xScale(pos.x)} cy={this.yScale(pos.y)} r={this.props.point_r} /> ); }.bind(this))}; </svg> </div> ); }

Kode ini melewati array this.props.data dan menambahkan elemen <circle> untuk setiap titik data. Sederhana.

Putus asa untuk pengujian UI Anda tidak lagi, dengan pengujian komponen React.
Menciak

Jika Anda ingin tahu lebih banyak tentang menggabungkan React dan d3.js untuk membuat komponen visualisasi data, itulah alasan bagus lainnya untuk membaca buku saya, React+d3.js .

Pengujian Komponen Bereaksi Otomatis: Lebih Mudah Dari Kedengarannya

Itu saja yang harus kita ketahui tentang menulis komponen front-end yang dapat diuji dengan React. Untuk melihat lebih banyak pengujian kode komponen React, lihat basis kode contoh pengujian React di Github, seperti yang disebutkan di atas.

Kami telah belajar bahwa:

  1. React memaksa kita untuk memodulasi dan merangkum.
  2. Ini membuat pengujian React UI mudah untuk diotomatisasi.
  3. Tes unit tidak cukup untuk front-end.
  4. Karma adalah pelari ujian yang hebat.
  5. Jest memiliki potensi, tetapi belum cukup sampai di sana. (Atau mungkin sekarang.)

Jika Anda menyukai artikel ini, ikuti saya di Twitter dan tinggalkan komentar di bawah. Terima kasih telah membaca, dan selamat menguji React!

Terkait: Cara Mengoptimalkan Komponen untuk Meningkatkan Performa React