Mocking/stubbing Mongoose Modell save-Methode
Einer einfachen Mongoose Modell:
import mongoose, { Schema } from 'mongoose';
const PostSchema = Schema({
title: { type: String },
postDate: { type: Date, default: Date.now }
}, { timestamps: true });
const Post = mongoose.model('Post', PostSchema);
export default Post;
Ich testen wollen, dieses Modell, aber ich ' m schlagen ein paar Straßensperren.
Meine aktuelle Skillung sieht ungefähr so aus (ein paar Sachen aus Platzgründen weggelassen):
import mongoose from 'mongoose';
import { expect } from 'chai';
import { Post } from '../../app/models';
describe('Post', () => {
beforeEach((done) => {
mongoose.connect('mongodb://localhost/node-test');
done();
});
describe('Given a valid post', () => {
it('should create the post', (done) => {
const post = new Post({
title: 'My test post',
postDate: Date.now()
});
post.save((err, doc) => {
expect(doc.title).to.equal(post.title)
expect(doc.postDate).to.equal(post.postDate);
done();
});
});
});
});
Jedoch, mit diesen werde ich schlagen, meine Datenbank jedes mal, wenn ich den test ausführen, das würde ich lieber vermeiden.
Ich habe versucht, mit Mockgoose, aber dann ist mein test wird nicht ausgeführt.
import mockgoose from 'mockgoose';
//in before or beforeEach
mockgoose(mongoose);
Den test stecken bleibt, und wirft eine Fehlermeldung: Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
ich habe versucht, die Erhöhung der timeout auf 20 Sekunden, aber das hat nichts lösen.
Weiter, warf ich Weg Mockgoose und versucht, mit Sinon zu stub die save
nennen.
describe('Given a valid post', () => {
it('should create the post', (done) => {
const post = new Post({
title: 'My test post',
postDate: Date.now()
});
const stub = sinon.stub(post, 'save', function(cb) { cb(null) })
post.save((err, post) => {
expect(stub).to.have.been.called;
done();
});
});
});
Diesem test geht, aber es irgendwie nicht viel Sinn für mich. Ich bin ganz neu bei stubbing, lustig, was du hast, ... und ich bin mir nicht sicher, ob dies der richtige Weg zu gehen. Ich bin stubbing der save
Methode auf post
und dann bin ich auch behaupten es wurde genannt, aber ich bin natürlich nannte es... Auch, ich kann nicht scheinen, um auf die Argumente der nicht-gekürzte Mungo-Methode zurückkehren würde. Ich möchte vergleichen die post
variable, um etwas, das die save
Methode gibt, wie im ersten test, wo ich auf die Datenbank. Ich habe versucht, eine paar von Methoden aber Sie alle fühlen sich ganz hackish. Es muss eine saubere Art und Weise, nicht?
Paar Fragen:
-
Sollte ich ja vermeiden, auf die wie bei einer Datenbank habe ich immer überall gelesen? Meinem ersten Beispiel funktioniert gut und ich konnte löschen Sie die Datenbank nach jedem Lauf. Allerdings ist es nicht wirklich das Gefühl mir Recht sein.
-
Wie würde ich die stub-die save-Methode aus dem Mungo-Modell und stellen Sie sicher, dass es tatsächlich testet, was ich will, um zu testen: speichern eines neuen Objekts in die db.
- Oleg ' s Antwort sieht gut aus, wenn Sie ein mockist TDDer, aber die meisten klassischen TDDers würde habe kein problem mit dem schlagen der Datenbank. Für eine Erklärung der mocks, stubs und mockist vs klassischen TDD siehe Martin Fowler ' s Artikel zum Thema.
- Am Ende des Tages tests sind da, um zu garantieren, code Qualität, es gibt also kein schreiben oder falsch, solange die Qualität nicht darunter leiden. Die Nachteile von basic-unit-tests schlagen der DB sind: (1) die Geschwindigkeit, (2) Komplexität für CI und individuellen Projekt-Entwickler, (3) test Nebenwirkungen übertragen über DB-Zustand, zwischen den einzelnen tests oder gleichzeitigen Prüfung läuft, die schwierig zu lösen, (4) Festsetzung Fehler bedeutet zusätzlichen Aufwand für die Entwickler, im worst case externe ressourcenabhängigkeit. Es ist nichts falsch über all das, aber wirklich zur integration tests nur. Ich würde klar trennen die beiden.
- Vergessen zu erwähnen: danke für den link zu einem sehr interessanten Artikel von Martin Fowler!
- Ich Stimme mit all dem. Ich persönlich bin gut mit nicht mit unit-tests für die Persistenz-bezogenen code und Integrations-tests nur. Dies ergibt sich aus der "Verwendung realer Gegenstände wenn möglich" - Mentalität der klassischen TDD. Wollte ich nur geben, dass Sicht und einige Hintergrundinformationen, die auf den Fragesteller, da er sagt, dass er neu stubbing/mocking und möglicherweise nicht bewusst sein, integration-Tests bei allen.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Die Grundlagen
In unit-Tests sollte man nicht auf die DB. Ich denken konnte, mit einer Ausnahme: durch eine in-memory-DB, aber auch das liegt bereits im Bereich integration-Prüfung, wie Sie bräuchte nur den Zustand, gespeichert in der Speicher für komplexe Prozesse (und damit nicht wirklich Funktionseinheiten). Also, ja keine eigentliche DB.
Was Sie testen möchten, in unit-tests ist, dass Sie Ihre business-Logik Ergebnisse in den korrekten API-Aufrufe an der Schnittstelle zwischen der Anwendung und der DB. Sie kann und sollte wohl davon ausgehen, dass die DB-API/Treiber Entwickler haben einen guten job gemacht zu testen, dass alles unterhalb der API verhält sich wie erwartet. Allerdings sollten Sie auch das cover in den tests, wie Sie Ihre business-Logik reagiert auf unterschiedliche gültigen API-Ergebnisse, wie erfolgreich spart, Ausfälle durch, um die Konsistenz der Daten, Fehler aufgrund von Verbindungsproblemen etc.
Dies bedeutet, dass das, was Sie brauchen und wollen, zu verhöhnen, ist alles was unterhalb der DB-Treiber-Schnittstelle. Sie müssten jedoch auf das Modell, dass das Verhalten, so dass Sie Ihre business Logik kann getestet werden, für alle Ergebnisse der DB-Aufrufe.
Leichter gesagt als getan sein, weil das bedeutet, dass Sie benötigen, um Zugriff auf die API über die Technologie, die Sie verwenden, und Sie müssen wissen, die API.
Die Realität des Mungos
Festhalten an den Grundlagen, die wir wollen, zu verspotten, die Anrufe, die durchgeführt, indem die zugrunde liegenden 'Treiber', dass mongoose verwendet. Vorausgesetzt, es ist Knoten-mongodb-native wir müssen mock aus dieser Aufrufe. Verstehen das zusammenspiel zwischen Mungo und der native Treiber ist nicht leicht, aber es kommt in der Regel nach unten, um die Methoden in
mongoose.Collection
weil letztere erstreckt sichmongoldb.Collection
und nicht implementieren Sie Methoden, wie Sieinsert
. Wenn wir in der Lage zu kontrollieren das Verhalteninsert
in diesem speziellen Fall, dann wissen wir, dass wir verspottet werden aus dem DB-Zugriff auf API-Ebene. Sie können die trace es in der Quelle der beiden Projekte, dassCollection.insert
ist wirklich die native-Treiber-Methode.Für Ihre speziellen Beispiel, das ich erstellt ein öffentliches Git-repository mit einem vollständigen Paket, aber ich werde alle die Elemente, die hier in die Antwort.
Die Lösung
Persönlich finde ich die "empfohlene" Weg der Zusammenarbeit mit mongoose ziemlich unbrauchbar: die Modelle werden in der Regel erstellt die Module, wo die entsprechenden schemas sind definiert, doch Sie müssen bereits eine Verbindung. Für die Zwecke, die mehrere verbindungen zu sprechen, die zu ganz verschiedenen mongodb-Datenbanken im selben Projekt und für Testzwecke das macht das Leben wirklich schwer. In der Tat, sobald Belange vollständig getrennt Mungo, zumindest für mich, wird fast unbrauchbar.
Also das erste, was ich Schaffe, ist die Paket-Beschreibung Datei, ein Modul mit einem schema und einem generischen Modell "generator":
Solches Modell generator hat seine Nachteile: es gibt Elemente, die können, müssen befestigt werden, um das Modell und würde es Sinn machen, legen Sie Sie in den gleichen Modul, in dem das schema erstellt wird. So finden sich in eine Allgemeine Möglichkeit, diejenigen hinzuzufügen, die ist ein bisschen tricky. Zum Beispiel könnte ein Modul export-post-Aktionen werden automatisch ausgeführt, wenn ein Modell erzeugt wird, die für eine gegebene Verbindung etc. (hacking).
Lassen Sie uns nun zu verhöhnen die API. Ich werde es einfach halten und nur Schein, was muss ich für die tests in Frage. Es ist wichtig, dass ich möchte, zu verhöhnen, als sich die API im Allgemeinen, nicht einzelne Methoden der einzelnen Instanzen. Letzteres könnte in einigen Fällen hilfreich sein, oder wenn sonst nichts hilft, aber ich brauchen würde, um Zugriff auf Objekte innerhalb meiner business-Logik (es sei denn, injiziert oder über einige factory-Muster), und dies würde bedeuten, ändern die wichtigste Quelle. Zur gleichen Zeit, machten Sie sich über die API in one place hat einen Nachteil: es ist eine generische Lösung, die wahrscheinlich erfolgreiche Ausführung. Für die Prüfung Fehler-Fällen, die Verspottung in den Fällen, in den tests selbst benötigt werden könnten, dann aber in Ihrem business-Logik, die Sie möglicherweise nicht haben einen direkten Zugang zu der Instanz von z.B.
post
geschaffen, die tief im inneren.So, lassen Sie uns einen Blick auf den Allgemeinen Fall der Spott erfolgreiche API-Aufruf:
In der Regel, so lange wie Modelle erstellt werden nach ändern Mungo, ist es denkbar, dass die oben verspottet werden pro test-Grundlage, um Verhalten zu simulieren. Stellen Sie sicher, um wieder auf das ursprüngliche Verhalten, jedoch vor jedem test!
Schließlich ist dies, wie unsere tests für alle möglichen Daten speichern Operationen Aussehen könnte. Achten, diese sind nicht spezifisch für unsere
Post
Modell und könnte getan werden, für alle anderen Modelle mit exakt der gleichen mock im Ort.Ist es wichtig zu beachten, dass wir noch in der Testphase, die sehr low-level-Funktionalität, aber wir können mit dieser Ansatz für die Prüfung jeglicher business-Logik, die verwendet
Post.create
oderpost.save
intern.Das Letzte bit, lassen Sie die tests ausführen:
Ich muss sagen, dies ist kein Spaß, es zu tun auf diese Weise. Aber so ist es wirklich Reine unit-Tests der business-Logik ohne in-memory-oder real-DBs und ziemlich generisch.
mongoose
Zeug, ohne eine Verbindung zum eigentlichendb
. Ist die Lösung oben wirklichdb
isoliert? wasvar conn = mongoose.createConnection();
dort tut?Wenn das, was Sie wollen, ist test
static's
undmethod's
bestimmter Mongoose Modell, würde ich empfehlen, Sie zu verwenden sinon und sinon-Mungo. (Ich denke, es ist kompatibel mit chai)Diese Weise werden Sie nicht brauchen, um zu verbinden, Mongo DB.
Folgen Sie Ihrem Beispiel, angenommen, Sie haben eine statische Methode
findLast
Dann, um zu testen, diese Methode
Finden Sie funktionierende (und einfache) Beispiele auf der sinon-Mungo repo.