Mirage JS Deep Dive: Menggunakan Mirage JS Dan Cypress Untuk Pengujian UI (Bagian 4)
Diterbitkan: 2022-03-10Salah satu kutipan favorit saya tentang pengujian perangkat lunak adalah dari dokumentasi Flutter. Ia mengatakan:
“Bagaimana Anda dapat memastikan bahwa aplikasi Anda terus berfungsi saat Anda menambahkan lebih banyak fitur atau mengubah fungsi yang ada? Dengan menulis tes.”
Pada catatan itu, bagian terakhir dari seri Mirage JS Deep Dive ini akan fokus pada penggunaan Mirage untuk menguji aplikasi front-end JavaScript Anda.
Catatan : Artikel ini mengasumsikan lingkungan Cypress. Cypress adalah kerangka kerja pengujian untuk pengujian UI. Namun, Anda dapat mentransfer pengetahuan di sini ke lingkungan atau kerangka kerja pengujian UI apa pun yang Anda gunakan.
Baca Bagian Seri Sebelumnya :
- Bagian 1: Memahami Model dan Asosiasi Mirage JS
- Bagian 2: Memahami Pabrik, Perlengkapan, dan Serializer
- Bagian 3: Memahami Waktu, Respons, dan Passthrough
Primer Tes UI
Uji UI atau Antarmuka Pengguna adalah bentuk pengujian penerimaan yang dilakukan untuk memverifikasi alur pengguna aplikasi front-end Anda. Penekanan dari jenis pengujian perangkat lunak ini adalah pada pengguna akhir yaitu orang yang sebenarnya yang akan berinteraksi dengan aplikasi web Anda di berbagai perangkat mulai dari desktop, laptop hingga perangkat seluler. Pengguna ini akan berinteraksi atau berinteraksi dengan aplikasi Anda menggunakan perangkat input seperti keyboard, mouse, atau layar sentuh. Oleh karena itu, pengujian UI ditulis untuk meniru interaksi pengguna dengan aplikasi Anda sedekat mungkin.
Mari kita ambil sebuah situs web e-commerce misalnya. Skenario pengujian UI yang khas adalah:
- Pengguna dapat melihat daftar produk saat mengunjungi beranda.
Skenario pengujian UI lainnya mungkin:
- Pengguna dapat melihat nama produk pada halaman detail produk.
- Pengguna dapat mengklik tombol "tambahkan ke troli".
- Pengguna dapat checkout.
Anda mendapatkan ide, kan?
Dalam membuat Tes UI, Anda sebagian besar akan mengandalkan status back-end Anda, yaitu apakah itu mengembalikan produk atau kesalahan? Peran yang dimainkan Mirage dalam hal ini adalah membuat status server tersebut tersedia untuk Anda sesuaikan sesuai kebutuhan. Jadi, alih-alih membuat permintaan aktual ke server produksi Anda dalam pengujian UI, Anda membuat permintaan ke server tiruan Mirage.
Untuk bagian selanjutnya dari artikel ini, kami akan melakukan pengujian UI pada UI aplikasi web e-commerce fiktif. Jadi mari kita mulai.
Tes UI Pertama kami
Seperti yang dinyatakan sebelumnya, artikel ini mengasumsikan lingkungan Cypress. Cypress membuat pengujian UI di web menjadi cepat dan mudah. Anda dapat mensimulasikan klik dan navigasi dan Anda dapat mengunjungi rute secara terprogram di aplikasi Anda. Lihat dokumen untuk informasi lebih lanjut tentang Cypress.
Jadi, dengan asumsi Cypress dan Mirage tersedia untuk kita, mari kita mulai dengan mendefinisikan fungsi proxy untuk permintaan API Anda. Kita dapat melakukannya di file support/index.js
dari penyiapan Cypress kita. Cukup tempel kode berikut di:
// cypress/support/index.js Cypress.on("window:before:load", (win) => { win.handleFromCypress = function (request) { return fetch(request.url, { method: request.method, headers: request.requestHeaders, body: request.requestBody, }).then((res) => { let content = res.headers.map["content-type"] === "application/json" ? res.json() : res.text() return new Promise((resolve) => { content.then((body) => resolve([res.status, res.headers, body])) }) }) } })
Kemudian, di file bootstrap aplikasi Anda ( main.js
untuk Vue, index.js
untuk React), kami akan menggunakan Mirage untuk mem-proksi permintaan API aplikasi Anda ke fungsi handleFromCypress
hanya saat Cypress sedang berjalan. Berikut adalah kode untuk itu:
import { Server, Response } from "miragejs" if (window.Cypress) { new Server({ environment: "test", routes() { let methods = ["get", "put", "patch", "post", "delete"] methods.forEach((method) => { this[method]("/*", async (schema, request) => { let [status, headers, body] = await window.handleFromCypress(request) return new Response(status, headers, body) }) }) }, }) }
Dengan pengaturan itu, kapan saja Cypress berjalan, aplikasi Anda tahu untuk menggunakan Mirage sebagai server tiruan untuk semua permintaan API.
Mari kita lanjutkan menulis beberapa tes UI. Kami akan mulai dengan menguji beranda kami untuk melihat apakah ada 5 produk yang ditampilkan. Untuk melakukan ini di Cypress, kita perlu membuat file homepage.test.js
di folder tests
di root direktori proyek Anda. Selanjutnya, kami akan memberi tahu Cypress untuk melakukan hal berikut:
- Kunjungi beranda yaitu
/
rute - Kemudian tegaskan jika memiliki elemen li dengan kelas
product
dan juga periksa apakah jumlahnya 5.
Berikut kodenya:
// homepage.test.js it('shows the products', () => { cy.visit('/'); cy.get('li.product').should('have.length', 5); });
Anda mungkin menduga bahwa pengujian ini akan gagal karena kami tidak memiliki server produksi yang mengembalikan 5 produk ke aplikasi front-end kami. Jadi apa yang kita lakukan? Kami mengejek server di Mirage! Jika kami membawa Mirage, itu dapat mencegat semua panggilan jaringan dalam pengujian kami. Mari lakukan ini di bawah ini dan mulai server Mirage sebelum setiap pengujian di fungsi beforeEach
dan juga matikan di fungsi afterEach
. Fungsi beforeEach
dan afterEach
keduanya disediakan oleh Cypress dan tersedia sehingga Anda dapat menjalankan kode sebelum dan sesudah setiap pengujian dijalankan di rangkaian pengujian Anda — itulah namanya. Jadi mari kita lihat kode untuk ini:
// homepage.test.js import { Server } from "miragejs" let server beforeEach(() => { server = new Server() }) afterEach(() => { server.shutdown() }) it("shows the products", function () { cy.visit("/") cy.get("li.product").should("have.length", 5) })
Oke, kita menuju suatu tempat; kami telah mengimpor Server dari Mirage dan kami memulai dan mematikannya di fungsi beforeEach
dan afterEach
masing-masing. Mari kita mengejek sumber daya produk kita.
// homepage.test.js import { Server, Model } from 'miragejs'; let server; beforeEach(() => { server = new Server({ models: { product: Model, }, routes() { this.namespace = 'api'; this.get('products', ({ products }, request) => { return products.all(); }); }, }); }); afterEach(() => { server.shutdown(); }); it('shows the products', function() { cy.visit('/'); cy.get('li.product').should('have.length', 5); });
Catatan : Anda selalu dapat mengintip bagian-bagian sebelumnya dari seri ini jika Anda tidak memahami bit Mirage dari cuplikan kode di atas.
- Bagian 1: Memahami Model dan Asosiasi Mirage JS
- Bagian 2: Memahami Pabrik, Perlengkapan, dan Serializer
- Bagian 3: Memahami Waktu, Respons, dan Passthrough
Oke, kami telah mulai menyempurnakan contoh Server kami dengan membuat model produk dan juga dengan membuat pengendali rute untuk rute /api/products
. Namun, jika kami menjalankan pengujian kami, itu akan gagal karena kami belum memiliki produk apa pun di database Mirage.
Mari kita isi database Mirage dengan beberapa produk. Untuk melakukan ini, kita bisa menggunakan metode create()
pada instance server kita, tetapi membuat 5 produk dengan tangan tampaknya cukup membosankan. Harus ada cara yang lebih baik.
Ah ya, ada. Mari kita manfaatkan pabrik (seperti yang dijelaskan di bagian kedua seri ini). Kita perlu membuat pabrik produk kita seperti ini:
// homepage.test.js import { Server, Model, Factory } from 'miragejs'; let server; beforeEach(() => { server = new Server({ models: { product: Model, }, factories: { product: Factory.extend({ name(i) { return `Product ${i}` } }) }, routes() { this.namespace = 'api'; this.get('products', ({ products }, request) => { return products.all(); }); }, }); }); afterEach(() => { server.shutdown(); }); it('shows the products', function() { cy.visit('/'); cy.get('li.product').should('have.length', 5); });
Kemudian, terakhir, kita akan menggunakan createList()
untuk dengan cepat membuat 5 produk yang harus lulus pengujian kita.
Mari kita lakukan:
// homepage.test.js import { Server, Model, Factory } from 'miragejs'; let server; beforeEach(() => { server = new Server({ models: { product: Model, }, factories: { product: Factory.extend({ name(i) { return `Product ${i}` } }) }, routes() { this.namespace = 'api'; this.get('products', ({ products }, request) => { return products.all(); }); }, }); }); afterEach(() => { server.shutdown(); }); it('shows the products', function() { server.createList("product", 5) cy.visit('/'); cy.get('li.product').should('have.length', 5); });
Jadi ketika kami menjalankan pengujian kami, itu lulus!
Catatan : Setelah setiap pengujian, server Mirage dimatikan dan disetel ulang, jadi tidak ada status ini yang bocor di seluruh pengujian.
Menghindari Banyak Server Mirage
Jika Anda telah mengikuti seri ini, Anda akan melihat saat kami menggunakan Mirage dalam pengembangan untuk mencegat permintaan jaringan kami; kami memiliki file server.js
di root aplikasi kami tempat kami mengatur Mirage. Dalam semangat KERING (Jangan Ulangi Diri Sendiri), saya pikir akan lebih baik untuk menggunakan server itu daripada memiliki dua contoh terpisah dari Mirage untuk pengembangan dan pengujian. Untuk melakukan ini (jika Anda belum memiliki file server.js
), buat saja di direktori src proyek Anda.
Catatan : Struktur Anda akan berbeda jika Anda menggunakan kerangka kerja JavaScript tetapi ide umumnya adalah menyiapkan file server.js di root src proyek Anda.
Jadi dengan struktur baru ini, kami akan mengekspor fungsi di server.js
yang bertanggung jawab untuk membuat instance server Mirage kami. Mari kita lakukan itu:
// src/server.js export function makeServer() { /* Mirage code goes here */}
Mari selesaikan implementasi fungsi makeServer
dengan menghapus server Mirage JS yang kita buat di homepage.test.js
dan menambahkannya ke badan fungsi makeServer
:
import { Server, Model, Factory } from 'miragejs'; export function makeServer() { let server = new Server({ models: { product: Model, }, factories: { product: Factory.extend({ name(i) { return `Product ${i}`; }, }), }, routes() { this.namespace = 'api'; this.get('/products', ({ products }) => { return products.all(); }); }, seeds(server) { server.createList('product', 5); }, }); return server; }
Sekarang yang harus Anda lakukan adalah mengimpor makeServer
dalam pengujian Anda. Menggunakan instans Server Mirage tunggal lebih bersih; dengan cara ini Anda tidak perlu memelihara dua instans server untuk lingkungan pengembangan dan pengujian.
Setelah mengimpor fungsi makeServer
, pengujian kami sekarang akan terlihat seperti ini:
import { makeServer } from '/path/to/server'; let server; beforeEach(() => { server = makeServer(); }); afterEach(() => { server.shutdown(); }); it('shows the products', function() { server.createList('product', 5); cy.visit('/'); cy.get('li.product').should('have.length', 5); });
Jadi kami sekarang memiliki server Mirage pusat yang melayani kami dalam pengembangan dan pengujian. Anda juga dapat menggunakan fungsi makeServer
untuk memulai Mirage dalam pengembangan (lihat bagian pertama dari seri ini).
Kode Mirage Anda seharusnya tidak menemukan jalannya ke produksi. Oleh karena itu, tergantung pada pengaturan build Anda, Anda hanya perlu memulai Mirage selama mode pengembangan.
Catatan : Baca artikel saya tentang cara mengatur API Mocking dengan Mirage dan Vue.js untuk melihat bagaimana saya melakukannya di Vue sehingga Anda dapat mereplikasi dalam kerangka kerja front-end apa pun yang Anda gunakan.
Lingkungan Pengujian
Mirage memiliki dua lingkungan: development (default) dan test . Dalam mode pengembangan, server Mirage akan memiliki waktu respons default 400 ms (yang dapat Anda sesuaikan. Lihat artikel ketiga dari seri ini untuk itu), mencatat semua respons server ke konsol, dan memuat benih pengembangan.
Namun, dalam lingkungan pengujian, kami memiliki:
- 0 penundaan untuk menjaga pengujian kami cepat
- Mirage menekan semua log agar tidak mencemari log CI Anda
- Mirage juga akan mengabaikan fungsi
seeds()
sehingga data seed Anda dapat digunakan hanya untuk pengembangan tetapi tidak bocor ke pengujian Anda. Ini membantu menjaga tes Anda tetap deterministik.
Mari kita perbarui makeServer
kita sehingga kita dapat memperoleh manfaat dari lingkungan pengujian. Untuk melakukan itu, kami akan membuatnya menerima objek dengan opsi lingkungan (kami akan menetapkannya secara default ke pengembangan dan menimpanya dalam pengujian kami). server.js
kami sekarang akan terlihat seperti ini:
// src/server.js import { Server, Model, Factory } from 'miragejs'; export function makeServer({ environment = 'development' } = {}) { let server = new Server({ environment, models: { product: Model, }, factories: { product: Factory.extend({ name(i) { return `Product ${i}`; }, }), }, routes() { this.namespace = 'api'; this.get('/products', ({ products }) => { return products.all(); }); }, seeds(server) { server.createList('product', 5); }, }); return server; }
Perhatikan juga bahwa kami meneruskan opsi lingkungan ke instans server Mirage menggunakan steno properti ES6. Sekarang dengan ini, mari perbarui pengujian kami untuk mengganti nilai lingkungan yang akan diuji. Pengujian kami sekarang terlihat seperti ini:
import { makeServer } from '/path/to/server'; let server; beforeEach(() => { server = makeServer({ environment: 'test' }); }); afterEach(() => { server.shutdown(); }); it('shows the products', function() { server.createList('product', 5); cy.visit('/'); cy.get('li.product').should('have.length', 5); });
Pengujian AAA
Mirage mendorong standar untuk pengujian yang disebut pendekatan pengujian triple-A atau AAA. Ini adalah singkatan dari Arrange , Act dan Assert . Anda sudah bisa melihat struktur ini dalam pengujian kami di atas:
it("shows all the products", function () { // ARRANGE server.createList("product", 5) // ACT cy.visit("/") // ASSERT cy.get("li.product").should("have.length", 5) })
Anda mungkin perlu mematahkan pola ini tetapi 9 kali dari 10 itu akan berfungsi dengan baik untuk pengujian Anda.
Mari Menguji Kesalahan
Sejauh ini, kami telah menguji beranda kami untuk melihat apakah itu memiliki 5 produk, namun, bagaimana jika server sedang down atau ada yang salah dengan mengambil produk? Kami tidak perlu menunggu server mati untuk bekerja pada tampilan UI kami dalam kasus seperti itu. Kita cukup mensimulasikan skenario itu dengan Mirage.
Mari kembalikan 500 (Server error) saat pengguna berada di beranda. Seperti yang telah kita lihat di artikel sebelumnya, untuk menyesuaikan respons Mirage, kami menggunakan kelas Response. Mari impor dan tulis pengujian kami.
homepage.test.js import { Response } from "miragejs" it('shows an error when fetching products fails', function() { server.get('/products', () => { return new Response( 500, {}, { error: "Can't fetch products at this time" } ); }); cy.visit('/'); cy.get('div.error').should('contain', "Can't fetch products at this time"); });
Sungguh dunia yang fleksibel! Kami hanya mengganti respons yang akan dikembalikan Mirage untuk menguji bagaimana UI kami akan ditampilkan jika gagal mengambil produk. File homepage.test.js
keseluruhan kami sekarang akan terlihat seperti ini:
// homepage.test.js import { Response } from 'miragejs'; import { makeServer } from 'path/to/server'; let server; beforeEach(() => { server = makeServer({ environment: 'test' }); }); afterEach(() => { server.shutdown(); }); it('shows the products', function() { server.createList('product', 5); cy.visit('/'); cy.get('li.product').should('have.length', 5); }); it('shows an error when fetching products fails', function() { server.get('/products', () => { return new Response( 500, {}, { error: "Can't fetch products at this time" } ); }); cy.visit('/'); cy.get('div.error').should('contain', "Can't fetch products at this time"); });
Perhatikan bahwa modifikasi yang kami lakukan pada handler /api/products
hanya ada dalam pengujian kami. Itu berarti ini berfungsi seperti yang kami definisikan sebelumnya ketika Anda berada dalam mode pengembangan.
Jadi ketika kami menjalankan pengujian kami, keduanya harus lulus.
Catatan : Saya yakin perlu dicatat bahwa elemen yang kami minta di Cypress harus ada di UI front-end Anda. Cypress tidak membuat elemen HTML untuk Anda.
Menguji Halaman Detail Produk
Terakhir, mari kita uji UI halaman detail produk. Jadi inilah yang kami uji:
- Pengguna dapat melihat nama produk di halaman detail produk
Mari kita lakukan. Pertama, kami membuat pengujian baru untuk menguji aliran pengguna ini.
Berikut tesnya:
it("shows the product's name on the detail route", function() { let product = this.server.create('product', { name: 'Korg Piano', }); cy.visit(`/${product.id}`); cy.get('h1').should('contain', 'Korg Piano'); });
homepage.test.js
Anda akhirnya akan terlihat seperti ini.
// homepage.test.js import { Response } from 'miragejs'; import { makeServer } from 'path/to/server; let server; beforeEach(() => { server = makeServer({ environment: 'test' }); }); afterEach(() => { server.shutdown(); }); it('shows the products', function() { console.log(server); server.createList('product', 5); cy.visit('/'); cy.get('li.product').should('have.length', 5); }); it('shows an error when fetching products fails', function() { server.get('/products', () => { return new Response( 500, {}, { error: "Can't fetch products at this time" } ); }); cy.visit('/'); cy.get('div.error').should('contain', "Can't fetch products at this time"); }); it("shows the product's name on the detail route", function() { let product = server.create('product', { name: 'Korg Piano', }); cy.visit(`/${product.id}`); cy.get('h1').should('contain', 'Korg Piano'); });
Saat Anda menjalankan tes, ketiganya harus lulus.
Membungkus
Sangat menyenangkan menunjukkan kepada Anda bagian dalam Mirage JS dalam seri ini. Saya harap Anda telah lebih siap untuk mulai memiliki pengalaman pengembangan front-end yang lebih baik dengan menggunakan Mirage untuk mengejek server back-end Anda. Saya juga berharap Anda akan menggunakan pengetahuan dari artikel ini untuk menulis lebih banyak tes penerimaan/UI/end-to-end untuk aplikasi front-end Anda.
- Bagian 1: Memahami Model dan Asosiasi Mirage JS
- Bagian 2: Memahami Pabrik, Perlengkapan, dan Serializer
- Bagian 3: Memahami Waktu, Respons, dan Passthrough
- Bagian 4: Menggunakan Mirage JS Dan Cypress Untuk Pengujian UI