Integrasi dan Pengujian End-to-end Menjadi Mudah dengan Node.js dan MongoDB

Diterbitkan: 2022-03-11

Pengujian adalah bagian penting dalam membangun aplikasi Node.js yang tangguh. Pengujian yang tepat dapat dengan mudah mengatasi banyak kekurangan yang mungkin ditunjukkan oleh pengembang tentang solusi pengembangan Node.js.

Meskipun banyak pengembang berfokus pada cakupan 100% dengan pengujian unit, penting agar kode yang Anda tulis tidak hanya diuji secara terpisah. Integrasi dan pengujian ujung ke ujung memberi Anda kepercayaan diri ekstra dengan menguji bagian-bagian aplikasi Anda bersama-sama. Bagian-bagian ini mungkin bekerja dengan baik sendiri, tetapi dalam sistem yang besar, unit kode jarang bekerja secara terpisah.

Node.js dan MongoDB bersama-sama membentuk salah satu duo paling populer belakangan ini. Jika Anda adalah salah satu dari banyak orang yang menggunakannya, Anda beruntung.

Dalam artikel ini, Anda akan mempelajari cara menulis integrasi dan pengujian ujung-ke-ujung dengan mudah untuk aplikasi Node.js dan MongoDB Anda yang berjalan pada instance nyata dari database, semuanya tanpa perlu menyiapkan lingkungan yang rumit atau kode penyiapan/teardown yang rumit .

Anda akan melihat bagaimana paket mongo-unit membantu integrasi dan pengujian ujung ke ujung di Node.js. Untuk ikhtisar yang lebih komprehensif tentang pengujian integrasi Node.js, lihat artikel ini.

Berurusan dengan Database Nyata

Biasanya, untuk integrasi atau pengujian ujung ke ujung, skrip Anda harus terhubung ke database khusus yang sebenarnya untuk tujuan pengujian. Ini melibatkan penulisan kode yang berjalan di awal dan akhir setiap test case/suite untuk memastikan bahwa database dalam keadaan bersih yang dapat diprediksi.

Ini mungkin bekerja dengan baik untuk beberapa proyek, tetapi memiliki beberapa keterbatasan:

  • Lingkungan pengujian bisa sangat kompleks. Anda harus menjaga agar database tetap berjalan di suatu tempat. Ini sering membutuhkan upaya ekstra untuk menyiapkan dengan server CI.
  • Basis data dan operasinya bisa relatif lambat. Karena database akan menggunakan koneksi jaringan dan operasinya akan memerlukan aktivitas sistem file, mungkin tidak mudah untuk menjalankan ribuan tes dengan cepat.
  • Basis data mempertahankan status, dan sangat tidak nyaman untuk pengujian. Tes harus independen satu sama lain, tetapi menggunakan DB umum dapat membuat satu tes memengaruhi yang lain.

Di sisi lain, menggunakan database nyata membuat lingkungan pengujian sedekat mungkin dengan produksi. Ini dapat dilihat sebagai keuntungan khusus dari pendekatan ini.

Menggunakan Basis Data Nyata dalam Memori

Menggunakan database nyata untuk pengujian tampaknya memiliki beberapa tantangan. Tapi, keuntungan menggunakan database nyata terlalu bagus untuk diteruskan. Bagaimana kita bisa mengatasi tantangan dan mempertahankan keuntungan?

Menggunakan kembali solusi yang baik dari platform lain dan menerapkannya ke dunia Node.js bisa menjadi cara untuk pergi ke sini.

Proyek Java banyak menggunakan DBUnit dengan database dalam memori (misalnya, H2) untuk tujuan ini.

DBUnit terintegrasi dengan JUnit (java test runner) dan memungkinkan Anda menentukan status database untuk setiap rangkaian pengujian/pengujian, dll. DBUnit menghilangkan kendala yang dibahas di atas:

  • DBUnit dan H2 adalah pustaka Java, jadi Anda tidak perlu menyiapkan lingkungan tambahan. Semuanya berjalan di JVM.
  • Basis data dalam memori membuat manajemen status ini sangat cepat.
  • DBUnit membuat konfigurasi database menjadi sangat sederhana dan memungkinkan Anda untuk menjaga status database yang jelas untuk setiap kasus.
  • H2 adalah database SQL dan sebagian kompatibel dengan MySQL sehingga, dalam kasus besar, aplikasi dapat bekerja dengannya seperti halnya dengan database produksi.

Mengambil dari konsep ini, saya memutuskan untuk membuat sesuatu yang serupa untuk Node.js dan MongoDB: Mongo-unit.

Mongo-unit adalah paket Node.js yang dapat diinstal menggunakan NPM atau Yarn. Ini menjalankan MongoDB dalam memori. Itu membuat tes integrasi menjadi mudah dengan mengintegrasikan dengan baik dengan Mocha dan menyediakan API sederhana untuk mengelola status database.

Pustaka menggunakan paket NPM bawaan mongodb, yang berisi binari MongoDB bawaan untuk sistem operasi populer. Instans MongoDB ini dapat berjalan dalam mode dalam memori.

Memasang unit Mongo

Untuk menambahkan mongo-unit ke proyek Anda, Anda dapat menjalankan:

 npm install -D mongo-unit

atau

 yarn add mongo-unit

Dan, itu dia. Anda bahkan tidak perlu menginstal MongoDB di komputer Anda untuk menggunakan paket ini.

Menggunakan unit Mongo untuk Tes Integrasi

Bayangkan Anda memiliki aplikasi Node.js sederhana untuk mengelola tugas:

 // service.js const mongoose = require('mongoose') const mongoUrl = process.env.MONGO_URL || 'mongodb://localhost:27017/example' mongoose.connect(mongoUrl) const TaskSchema = new mongoose.Schema({ name: String, started: Date, completed: Boolean, }) const Task = mongoose.model('tasks', TaskSchema) module.exports = { getTasks: () => Task.find(), addTask: data => new Task(data).save(), deleteTask: taskId => Task.findByIdAndRemove(taskId) }

URL koneksi MongoDB tidak di-hard-code di sini. Seperti kebanyakan back-end aplikasi web, kami mengambilnya dari variabel lingkungan. Ini akan memungkinkan kami menggantinya dengan URL apa pun selama pengujian.

 const express = require('express') const bodyParser = require('body-parser') const service = require('./service') const app = express() app.use(bodyParser.json()) app.use(express.static(`${__dirname}/static`)) app.get('/example', (req, res) => { service.getTasks().then(tasks => res.json(tasks)) }) app.post('/example', (req, res) => { service.addTask(req.body).then(data => res.json(data)) }) app.delete('/example/:taskId', (req, res) => { service.deleteTask(req.params.taskId).then(data => res.json(data)) }) app.listen(3000, () => console.log('started on port 3000'))

Ini adalah cuplikan dari contoh aplikasi yang memiliki antarmuka pengguna. Kode untuk UI telah dihilangkan untuk singkatnya. Anda dapat melihat contoh lengkapnya di GitHub.

Mengintegrasikan dengan Mocha

Untuk membuat Mocha menjalankan tes integrasi terhadap unit mongo, kita perlu menjalankan instance database unit mongo sebelum kode aplikasi dimuat dalam konteks Node.js. Untuk melakukan ini, kita dapat menggunakan parameter mocha --require dan perpustakaan Mocha-prepare, yang memungkinkan Anda melakukan operasi asinkron dalam skrip yang diperlukan.

 // it-helper.js const prepare = require('mocha-prepare') const mongoUnit = require('mongo-unit') prepare(done => mongoUnit.start() .then(testMongoUrl => { process.env.MONGO_URL = testMongoUrl done() }))

Menulis Tes Integrasi

Langkah pertama adalah menambahkan tes ke database pengujian ( testData.json ):

 { "tasks": [ { "name": "test", "started": "2017-08-28T16:07:38.268Z", "completed": false } ] }

Langkah selanjutnya adalah menambahkan tes itu sendiri:

 const expect = require('chai').expect const mongoose = require('mongoose') const mongoUnit = require('../index') const service = require('./app/service') const testMongoUrl = process.env.MONGO_URL describe('service', () => { const testData = require('./fixtures/testData.json') beforeEach(() => mongoUnit.initDb(testMongoUrl, testData)) afterEach(() => mongoUnit.drop()) it('should find all tasks', () => { return service.getTasks() .then(tasks => { expect(tasks.length).to.equal(1) expect(tasks[0].name).to.equal('test') }) }) it('should create new task', () => { return service.addTask({ name: 'next', completed: false }) .then(task => { expect(task.name).to.equal('next') expect(task.completed).to.equal(false) }) .then(() => service.getTasks()) .then(tasks => { expect(tasks.length).to.equal(2) expect(tasks[1].name).to.equal('next') }) }) it('should remove task', () => { return service.getTasks() .then(tasks => tasks[0]._id) .then(taskId => service.deleteTask(taskId)) .then(() => service.getTasks()) .then(tasks => { expect(tasks.length).to.equal(0) }) }) })

Dan, voila!

Perhatikan bagaimana hanya ada beberapa baris kode yang berhubungan dengan penyiapan dan pembongkaran.

Seperti yang Anda lihat, sangat mudah untuk menulis tes integrasi menggunakan perpustakaan unit mongo. Kami tidak mengejek MongoDB itu sendiri, dan kami dapat menggunakan model Mongoose yang sama. Kami memiliki kendali penuh atas data database dan tidak kehilangan banyak performa pengujian karena MongoDB palsu berjalan di memori.

Ini juga memungkinkan kami untuk menerapkan praktik pengujian unit terbaik untuk pengujian integrasi:

  • Jadikan setiap tes independen dari tes lainnya. Kami memuat data baru sebelum setiap pengujian, memberi kami status yang sepenuhnya independen untuk setiap pengujian.
  • Gunakan status minimum yang diperlukan untuk setiap pengujian. Kita tidak perlu mengisi seluruh database. Kami hanya perlu mengatur data minimum yang diperlukan untuk setiap tes tertentu.
  • Kita dapat menggunakan kembali satu koneksi untuk database. Ini meningkatkan kinerja tes.

Sebagai bonus, kita bahkan dapat menjalankan aplikasi itu sendiri terhadap unit mongo. Ini memungkinkan kita untuk melakukan pengujian ujung ke ujung untuk aplikasi kita terhadap database tiruan.

Tes ujung ke ujung dengan Selenium

Untuk pengujian ujung ke ujung, kami akan menggunakan pelari uji Selenium WebDriver dan Hermione E2E.

Pertama, kita akan bootstrap driver dan test runner:

 const mongoUnit = require('mongo-unit') const selenium = require('selenium-standalone') const Hermione = require('hermione') const hermione = new Hermione('./e2e/hermione.conf.js') //hermione config seleniumInstall() //make sure selenium is installed .then(seleniumStart) //start selenium web driver .then(mongoUnit.start) // start mongo unit .then(testMongoUrl => { process.env.MONGO_URL = testMongoUrl //store mongo url }) .then(() => { require('./index.js') //start application }) .then(delay(1000)) // wait a second till application is started .then(() => hermione.run('', hermioneOpts)) // run hermiona e2e tests .then(() => process.exit(0)) .catch(() => process.exit(1))

Kami juga memerlukan beberapa fungsi pembantu (penanganan kesalahan dihapus untuk singkatnya):

 function seleniumInstall() { return new Promise(resolve => selenium.install({}, resolve)) } function seleniumStart() { return new Promise(resolve => selenium.start(resolve)) } function delay(timeout) { return new Promise(resolve => setTimeout(resolve, timeout)) }

Setelah mengisi database dengan beberapa data dan membersihkannya setelah pengujian selesai, kami dapat menjalankan pengujian pertama kami:

 const expect = require('chai').expect const co = require('co') const mongoUnit = require('../index') const testMongoUrl = process.env.MONGO_URL const DATA = require('./fixtures/testData.json') const ui = { task: '.task', remove: '.task .remove', name: '#name', date: '#date', addTask: '#addTask' } describe('Tasks', () => { beforeEach(function () { return mongoUnit.initDb(testMongoUrl, DATA) .then(() => this.browser.url('http://localhost:3000')) }) afterEach(() => mongoUnit.dropDb(testMongoUrl)) it('should display list of tasks', function () { const browser = this.browser return co(function* () { const tasks = yield browser.elements(ui.task) expect(tasks.length, 1) }) }) it('should create task', function () { const browser = this.browser return co(function* () { yield browser.element(ui.name).setValue('test') yield browser.element(ui.addTask).click() const tasks = yield browser.elements(ui.task) expect(tasks.length, 2) }) }) it('should remove task', function () { const browser = this.browser return co(function* () { yield browser.element(ui.remove).click() const tasks = yield browser.elements(ui.task) expect(tasks.length, 0) }) }) })

Seperti yang Anda lihat, pengujian end-to-end terlihat sangat mirip dengan pengujian integrasi.

Bungkus

Integrasi dan pengujian ujung ke ujung penting untuk aplikasi skala besar apa pun. Aplikasi Node.js, khususnya, dapat sangat diuntungkan dari pengujian otomatis. Dengan mongo-unit, Anda dapat menulis integrasi dan pengujian ujung ke ujung tanpa mengkhawatirkan semua tantangan yang menyertai pengujian tersebut.

Anda dapat menemukan contoh lengkap tentang cara menggunakan unit mongo di GitHub.

Bacaan Lebih Lanjut di Blog Teknik Toptal:

  • Membangun Node.js/TypeScript REST API, Bagian 3: MongoDB, Otentikasi, dan Pengujian Otomatis