Dasar-dasarnya
Dalam pengujian unit, seseorang tidak boleh menekan DB. Saya dapat memikirkan satu pengecualian:mengenai DB dalam memori, tetapi bahkan itu sudah berada di area pengujian integrasi karena Anda hanya perlu status yang disimpan dalam memori untuk proses yang kompleks (dan dengan demikian bukan unit fungsionalitas yang sebenarnya). Jadi, ya bukan DB sebenarnya.
Apa yang ingin Anda uji dalam pengujian unit adalah bahwa logika bisnis Anda menghasilkan panggilan API yang benar pada antarmuka antara aplikasi Anda dan DB. Anda dapat dan mungkin harus berasumsi bahwa DB API/pengembang driver telah melakukan pengujian pekerjaan yang baik bahwa segala sesuatu di bawah API berperilaku seperti yang diharapkan. Namun, Anda juga ingin membahas dalam pengujian Anda bagaimana logika bisnis Anda bereaksi terhadap hasil API valid yang berbeda seperti penyimpanan yang berhasil, kegagalan karena konsistensi data, kegagalan karena masalah koneksi, dll.
Ini berarti bahwa yang Anda butuhkan dan ingin tiru adalah semua yang ada di bawah antarmuka driver DB. Namun, Anda perlu memodelkan perilaku itu sehingga logika bisnis Anda dapat diuji untuk semua hasil panggilan DB.
Lebih mudah diucapkan daripada dilakukan karena ini berarti Anda harus memiliki akses ke API melalui teknologi yang Anda gunakan dan Anda perlu mengetahui API tersebut.
Realitas luwak
Berpegang pada dasar-dasar kami ingin mengejek panggilan yang dilakukan oleh 'driver' yang mendasari yang digunakan luwak. Dengan asumsi itu adalah node-mongodb-native
kita perlu mengejek panggilan itu. Memahami interaksi penuh antara luwak dan driver asli tidak mudah, tetapi umumnya bermuara pada metode di mongoose.Collection
karena yang terakhir memperluas mongoldb.Collection
dan tidak menerapkan kembali metode seperti insert
. Jika kita dapat mengontrol perilaku insert
dalam kasus khusus ini, maka kami tahu kami mengejek akses DB di level API. Anda dapat melacaknya di sumber kedua proyek, yaitu Collection.insert
benar-benar metode driver asli.
Untuk contoh khusus Anda, saya membuat repositori Git publik dengan paket lengkap, tetapi saya akan memposting semua elemen di sini dalam jawabannya.
Solusinya
Secara pribadi saya menemukan cara "disarankan" untuk bekerja dengan luwak cukup tidak dapat digunakan:model biasanya dibuat dalam modul di mana skema yang sesuai ditentukan, namun mereka sudah membutuhkan koneksi. Untuk tujuan memiliki banyak koneksi untuk berbicara dengan database mongodb yang sama sekali berbeda dalam proyek yang sama dan untuk tujuan pengujian ini membuat hidup sangat sulit. Faktanya, segera setelah kekhawatiran sepenuhnya terpisah, luwak, setidaknya bagi saya, menjadi hampir tidak dapat digunakan.
Jadi hal pertama yang saya buat adalah file deskripsi paket, modul dengan skema dan "generator model" generik:
{
"name": "xxx",
"version": "0.1.0",
"private": true,
"main": "./src",
"scripts": {
"test" : "mocha --recursive"
},
"dependencies": {
"mongoose": "*"
},
"devDependencies": {
"mocha": "*",
"chai": "*"
}
}
var mongoose = require("mongoose");
var PostSchema = new mongoose.Schema({
title: { type: String },
postDate: { type: Date, default: Date.now }
}, {
timestamps: true
});
module.exports = PostSchema;
var model = function(conn, schema, name) {
var res = conn.models[name];
return res || conn.model.bind(conn)(name, schema);
};
module.exports = {
PostSchema: require("./post"),
model: model
};
Generator model seperti itu memiliki kekurangan:ada elemen yang mungkin perlu dilampirkan ke model dan masuk akal untuk menempatkannya di modul yang sama tempat skema dibuat. Jadi menemukan cara umum untuk menambahkan itu agak rumit. Misalnya, modul dapat mengekspor tindakan pasca untuk dijalankan secara otomatis saat model dibuat untuk koneksi tertentu, dll. (peretasan).
Sekarang mari kita mengejek API. Saya akan membuatnya tetap sederhana dan hanya akan mengejek apa yang saya butuhkan untuk tes yang dimaksud. Sangat penting bahwa saya ingin mengejek API secara umum, bukan metode individual dari masing-masing instance. Yang terakhir mungkin berguna dalam beberapa kasus, atau ketika tidak ada yang membantu, tetapi saya perlu memiliki akses ke objek yang dibuat di dalam logika bisnis saya (kecuali disuntikkan atau disediakan melalui beberapa pola pabrik), dan ini berarti memodifikasi sumber utama. Pada saat yang sama, mengejek API di satu tempat memiliki kelemahan:ini adalah solusi umum, yang mungkin akan mengimplementasikan eksekusi yang berhasil. Untuk menguji kasus kesalahan, mengejek dalam contoh dalam pengujian itu sendiri mungkin diperlukan, tetapi kemudian dalam logika bisnis Anda, Anda mungkin tidak memiliki akses langsung ke contoh mis. post
diciptakan jauh di dalam.
Jadi, mari kita lihat kasus umum mengejek panggilan API yang berhasil:
var mongoose = require("mongoose");
// this method is propagated from node-mongodb-native
mongoose.Collection.prototype.insert = function(docs, options, callback) {
// this is what the API would do if the save succeeds!
callback(null, docs);
};
module.exports = mongoose;
Umumnya, selama model dibuat setelah memodifikasi luwak, dapat dipikirkan bahwa ejekan di atas dilakukan berdasarkan per pengujian untuk mensimulasikan perilaku apa pun. Namun, pastikan untuk kembali ke perilaku aslinya, sebelum setiap pengujian!
Akhirnya seperti inilah pengujian kami untuk semua kemungkinan operasi penyimpanan data. Perhatikan, ini tidak khusus untuk Post
kami model dan dapat dilakukan untuk semua model lain dengan tiruan yang sama persis di tempat.
// now we have mongoose with the mocked API
// but it is essential that our models are created AFTER
// the API was mocked, not in the main source!
var mongoose = require("./mock"),
assert = require("assert");
var underTest = require("../src");
describe("Post", function() {
var Post;
beforeEach(function(done) {
var conn = mongoose.createConnection();
Post = underTest.model(conn, underTest.PostSchema, "Post");
done();
});
it("given valid data post.save returns saved document", function(done) {
var post = new Post({
title: 'My test post',
postDate: Date.now()
});
post.save(function(err, doc) {
assert.deepEqual(doc, post);
done(err);
});
});
it("given valid data Post.create returns saved documents", function(done) {
var post = new Post({
title: 'My test post',
postDate: 876543
});
var posts = [ post ];
Post.create(posts, function(err, docs) {
try {
assert.equal(1, docs.length);
var doc = docs[0];
assert.equal(post.title, doc.title);
assert.equal(post.date, doc.date);
assert.ok(doc._id);
assert.ok(doc.createdAt);
assert.ok(doc.updatedAt);
} catch (ex) {
err = ex;
}
done(err);
});
});
it("Post.create filters out invalid data", function(done) {
var post = new Post({
foo: 'Some foo string',
postDate: 876543
});
var posts = [ post ];
Post.create(posts, function(err, docs) {
try {
assert.equal(1, docs.length);
var doc = docs[0];
assert.equal(undefined, doc.title);
assert.equal(undefined, doc.foo);
assert.equal(post.date, doc.date);
assert.ok(doc._id);
assert.ok(doc.createdAt);
assert.ok(doc.updatedAt);
} catch (ex) {
err = ex;
}
done(err);
});
});
});
Penting untuk dicatat bahwa kami masih menguji fungsionalitas tingkat yang sangat rendah, tetapi kami dapat menggunakan pendekatan yang sama ini untuk menguji logika bisnis apa pun yang menggunakan Post.create
atau post.save
secara internal.
Bagian terakhir, mari kita jalankan tesnya:
> [email protected] test /Users/osklyar/source/web/xxx
> mocha --recursive
Post
✓ given valid data post.save returns saved document
✓ given valid data Post.create returns saved documents
✓ Post.create filters out invalid data
3 passing (52ms)
Saya harus mengatakan, ini tidak menyenangkan untuk melakukannya dengan cara itu. Tapi cara ini benar-benar murni pengujian unit logika bisnis tanpa memori atau DB nyata dan cukup umum.