基本
ユニットテストでは、DBにヒットしないでください。 1つの例外を考えることができます。メモリ内のDBにアクセスすることですが、複雑なプロセス(したがって実際には機能の単位ではない)の場合にメモリに保存された状態のみが必要になるため、それでも統合テストの領域にすでにあります。したがって、実際のDBはありません。
単体テストでテストしたいのは、ビジネスロジックがアプリケーションとDBの間のインターフェイスで正しいAPI呼び出しをもたらすことです。 DB API /ドライバー開発者は、APIの下にあるすべてのものが期待どおりに動作することをテストするために、適切な作業を行ったと想定できます。ただし、テストでは、保存の成功、データの一貫性による失敗、接続の問題による失敗など、さまざまな有効なAPI結果にビジネスロジックがどのように反応するかもカバーする必要があります。
これは、必要であり、モックしたいのは、DBドライバーインターフェイスの下にあるすべてのものであることを意味します。ただし、DB呼び出しのすべての結果についてビジネスロジックをテストできるように、その動作をモデル化する必要があります。
これは、使用するテクノロジーを介してAPIにアクセスする必要があり、APIを知っている必要があることを意味するため、言うのは簡単です。
マングースの現実
基本に固執して、マングースが使用する基礎となる「ドライバー」によって実行される呼び出しをモックしたいと思います。 node-mongodb-native
であると仮定します それらの呼び出しをモックアウトする必要があります。マングースとネイティブドライバーの完全な相互作用を理解するのは簡単ではありませんが、一般的にはmongoose.Collection
のメソッドに帰着します。 後者はmongoldb.Collection
を拡張するためです およびしない insert
のような再実装メソッド 。 insert
の動作を制御できる場合 この特定のケースでは、APIレベルでDBアクセスをモックアウトしたことがわかります。 Collection.insert
という両方のプロジェクトのソースで追跡できます。 本当にネイティブドライバメソッドです。
あなたの特定の例では、パブリックGitリポジトリ を作成しました 完全なパッケージが含まれていますが、ここですべての要素を回答に掲載します。
解決策
個人的には、マングースを扱う「推奨される」方法はまったく使用できないと思います。モデルは通常、対応するスキーマが定義されているモジュールで作成されますが、すでに接続が必要です。同じプロジェクト内の完全に異なるmongodbデータベースと通信するための複数の接続を使用する目的で、またテスト目的で、これは人生を非常に困難にします。実際、懸念が完全に分離されるとすぐに、少なくとも私にとっては、マングースはほとんど使用できなくなります。
したがって、私が最初に作成するのは、パッケージ記述ファイル、スキーマと一般的な「モデルジェネレーター」を備えたモジュールです。
{
"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
};
このようなモデルジェネレータには欠点があります。モデルにアタッチする必要のある要素があり、スキーマが作成されるのと同じモジュールにそれらを配置することは理にかなっています。したがって、それらを追加する一般的な方法を見つけるのは少し注意が必要です。たとえば、モジュールはポストアクションをエクスポートして、特定の接続など(ハッキング)に対してモデルが生成されたときに自動的に実行されるようにすることができます。
それでは、APIをモックしてみましょう。シンプルに保ち、問題のテストに必要なものだけをモックアップします。個々のインスタンスの個々のメソッドではなく、一般的にAPIをモックアウトしたいことが重要です。後者は、場合によっては役立つ場合もあれば、他に何も役に立たない場合もありますが、ビジネスロジック内で作成されたオブジェクトにアクセスする必要があります(ファクトリパターンを介して注入または提供された場合を除く)。これは、メインソースを変更することを意味します。同時に、APIを1か所でモックすることには欠点があります。それは一般的なソリューションであり、おそらく正常な実行を実装するでしょう。エラーケースをテストする場合、テスト自体のインスタンスのモックが必要になる可能性がありますが、ビジネスロジック内では、たとえば、のインスタンスに直接アクセスできない場合があります。 post
奥深くに作成されました。
それでは、成功したAPI呼び出しをあざける一般的なケースを見てみましょう:
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;
一般的に、モデルが後に作成される限り マングースを変更すると、上記のモックは、あらゆる動作をシミュレートするためにテストごとに実行されると考えられます。ただし、すべてのテストの前に、必ず元の動作に戻してください。
最後に、これは、考えられるすべてのデータ保存操作のテストがどのように見えるかを示しています。注意してください、これらは私たちのPost
に固有のものではありません モデルであり、まったく同じモックが配置されている他のすべてのモデルに対して実行できます。
// 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);
});
});
});
まだ非常に低レベルの機能をテストしていることに注意する必要がありますが、Post.create
を使用するビジネスロジックをテストするためにこれと同じアプローチを使用できます。 またはpost.save
内部的に。
最後に、テストを実行してみましょう。
> [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)
私は言わなければならない、これはそのようにするのは楽しいことではありません。しかし、この方法では、メモリ内または実際のDBがなく、かなり一般的なビジネスロジックの純粋な単体テストです。