Knex: Wie schreibe ich Unit-Tests für Methoden, die Knex verwenden.

Erstellt am 21. Mai 2017  ·  28Kommentare  ·  Quelle: knex/knex

(Ursprünglich veröffentlicht in # 1659, hier zur weiteren Diskussion verschoben.)

Ich bin in diesem Fall etwas im Dunkeln - die Knex-Dokumente decken die Barebones von Transaktionen ab, aber wenn ich google "Verwenden von knex.js zum Testen von Einheiten" und "ava.js + knex" und "ava.js + Datenbank" Da ich nichts besonders lehrreiches finden konnte, um mir einen guten Weg zu zeigen, habe ich auf meine Ruby-Erfahrung zurückgegriffen, bei der das Umschließen eines Komponententests in eine Transaktion eine übliche Technik zum Zurücksetzen der Datenbank nach jedem Test ist.

@odigity Ich würde nicht empfehlen, dies zu tun, da Ihre Tests nur eine Verbindung anstelle des Verbindungspools verwenden und nicht die reale Nutzung Ihrer App darstellen.

Das kam mir in den Sinn, aber ich war bereit, dies zu akzeptieren, wenn es mir erlaubte, eine einfache Methode zum einmaligen Schreiben für die Implementierung der DB-Bereinigung nach dem Test zu verwenden. (Ich habe meinen Pool min / max auf 1 gesetzt, um sicherzugehen.) Wenn es eine Möglichkeit gibt, dies zu erreichen und gleichzeitig gleichzeitige Verbindungen zu unterstützen, werde ich das absolut akzeptieren.

Es ist auch so gut wie unmöglich zu implementieren, es sei denn, die Anwendung, die Sie testen, führt ihre Abfragen in derselben Transaktion aus, die im Testcode gestartet wurde (Sie müssten die Transaktion im Test starten, dann Ihre App starten und die erstellte Transaktion an die App übergeben, damit sie führt alle Abfragen zu derselben Transaktion durch, und verschachtelte Transaktionen verhalten sich möglicherweise weitgehend ...).

Ich habe gehofft / erwartet, dass es funktioniert:

1) In jedem Unit-Test erstelle ich eine Transaktion, die trx .

2) Ich benötige dann das Modul, das ich testen möchte, und übergebe das trx -Objekt an den Modulkonstruktor, damit es vom Modul verwendet wird, sodass alle Abfragen innerhalb der Transaktion auftreten.

3) Nachdem die Modulmethode zurückgegeben wurde (oder einen Fehler ausgelöst hat), führe ich meine Zusicherungen für den resultierenden Status der Datenbank aus und rufe dann trx.rollback() auf, um alles von Anfang an rückgängig zu machen und mich auf den nächsten Test vorzubereiten.

Das ist es, was ich erreichen möchte und wie ich es ursprünglich erreichen wollte. Ich bin gespannt auf mehr über:

1) Warum verstehe ich falsch, wie Knex.js funktioniert und verwendet werden sollte?

2) Best Practices für das Schreiben von Atom-Unit-Tests für Code, der die Datenbank berührt.

question

Hilfreichster Kommentar

Nein - habe nichts. Zusammenfassung in chronologischer Reihenfolge der Quellen:

2016-04-21 https://medium.com/@jomaora/knex -bookshelf-mocks-and-unit-tests-cca627565d3

Strategie: Verwenden Sie Mock-Knex . Ich möchte die Datenbank nicht verspotten - ich möchte meine Methoden gegen eine tatsächliche MySQL-Datenbank testen, um ein korrektes Verhalten sicherzustellen - aber ich habe mir trotzdem mock-knex ... es ist möglicherweise nur die am schlechtesten gestaltete Bibliothek Ich habe jemals begegnet. :(

2016-04-28 http://mherman.org/blog/2016/04/28/test-driven-development-with-node/

Strategie: Rollback / Remigrate / Reseed DB nach jedem Test. Das scheint für jeden Test viel Aufwand zu bedeuten und wird furchtbar langsam ablaufen. Parallelität könnte durch Generieren einer UUID für den DB-Namen jedes Tests erreicht werden, aber das scheint im Vergleich zur Eleganz von Transaktionen eine schreckliche Lösung zu sein ...

23.09.2015 http://stackoverflow.com/a/32749601/210867

Strategie: Verwenden Sie SQLite zum Testen, erstellen / zerstören Sie die Datenbank für jeden Test. Ich habe beide Gründe behandelt, warum ich diesen Ansatz oben nicht mag.

Also ... immer noch nach Vorschlägen fischen und zusätzliche Anleitungen, wie Knex-Transaktionen tatsächlich funktionieren und wie man sie richtig auf meinen Anwendungsfall anwendet.

Alle 28 Kommentare

Ich habe anscheinend letzte Nacht schlecht gegoogelt, weil ich es gerade noch einmal mit "Wie schreibe ich Atom-Unit-Tests mit knex.js" versucht habe und einige interessante Ergebnisse erhalte. Ich werde sie jetzt durchlesen. Werde wieder posten, wenn ich etwas lerne.

Nein - habe nichts. Zusammenfassung in chronologischer Reihenfolge der Quellen:

2016-04-21 https://medium.com/@jomaora/knex -bookshelf-mocks-and-unit-tests-cca627565d3

Strategie: Verwenden Sie Mock-Knex . Ich möchte die Datenbank nicht verspotten - ich möchte meine Methoden gegen eine tatsächliche MySQL-Datenbank testen, um ein korrektes Verhalten sicherzustellen - aber ich habe mir trotzdem mock-knex ... es ist möglicherweise nur die am schlechtesten gestaltete Bibliothek Ich habe jemals begegnet. :(

2016-04-28 http://mherman.org/blog/2016/04/28/test-driven-development-with-node/

Strategie: Rollback / Remigrate / Reseed DB nach jedem Test. Das scheint für jeden Test viel Aufwand zu bedeuten und wird furchtbar langsam ablaufen. Parallelität könnte durch Generieren einer UUID für den DB-Namen jedes Tests erreicht werden, aber das scheint im Vergleich zur Eleganz von Transaktionen eine schreckliche Lösung zu sein ...

23.09.2015 http://stackoverflow.com/a/32749601/210867

Strategie: Verwenden Sie SQLite zum Testen, erstellen / zerstören Sie die Datenbank für jeden Test. Ich habe beide Gründe behandelt, warum ich diesen Ansatz oben nicht mag.

Also ... immer noch nach Vorschlägen fischen und zusätzliche Anleitungen, wie Knex-Transaktionen tatsächlich funktionieren und wie man sie richtig auf meinen Anwendungsfall anwendet.

@odigity fasste die schlechten

Wir machen unsere "Unit" -Tests wie folgt:

  1. Starten Sie das System, initialisieren Sie die Datenbank und führen Sie Migrationen aus

  2. Vor jedem Test werden alle Tabellen und Sequenzen abgeschnitten (mit dem Paket knex-db-manager).

  3. Für den Testfall erforderliche Daten einfügen (wir verwenden knex-basiertes objection.js ORM für das, was es uns ermöglicht, verschachtelte Objekthierarchien mit einem einzigen Befehl einzufügen. Es weiß, wie Einfügungen optimiert werden, damit nicht für jede Zeile ein separates Einfügen durchgeführt werden muss in der Tabelle, aber normalerweise nur eine Einfügung pro Tabelle)

  4. Führen Sie 1 Test aus und fahren Sie mit Schritt 2 fort

Bei e2e-Tests haben wir die Methoden saveState / restoreState (mit pg_restore / pg_dump) implementiert, mit denen wir während des Testlaufs auf einen bestimmten Status zurücksetzen können, sodass wir den Testlauf nicht jedes Mal neu starten müssen, wenn ein Test nach dem Ausführen von 20 fehlschlägt Minuten von Tests.

Das ist ähnlich wie gestern, weil es einfach und geradlinig ist und ich Fortschritte machen musste.

Haben Sie die Strategie, Tests in Transaktionen einzuschließen und danach zurückzusetzen, als schnellere Alternative zum Abschneiden aller Tabellen in Betracht gezogen? Das scheint mir das Ideal zu sein, wenn ich die Umsetzung herausfinden kann.

Ich nehme an, wenn Sie Datenbank- und Anwendungscode im selben Prozess ausführen, können Sie eine Transaktion im Testcode erstellen und diese Transaktion dann als Ihre Knex-Instanz registrieren (Knex-Instanz und -Transaktion sehen in einigen Fällen etwas anders aus, aber normalerweise können Sie die Transaktion verwenden Instanz wie normale Knex-Instanz).

Dann starten Sie Ihren Anwendungscode, der die Transaktion anstelle der normalen gepoolten Knex-Instanz abruft, und beginnen damit, Abfragen durchzuführen.

Ziemlich genau so, wie Sie es in OP beschrieben haben. Wie Sie es beschrieben haben, klingt machbar.

Ich habe vor ein paar Jahren überlegt, Transaktionen zum Zurücksetzen der Datenbank in Tests zu verwenden, habe sie jedoch abgelehnt, weil ich das Verbindungspooling so nutzen möchte, dass es in Tests so funktioniert, wie es in App + Truncate / Init funktioniert. Das ist schnell genug für uns.

Gibt es keine Möglichkeit, eine Verbindungsaffinität für die Dauer der Transaktion zu erreichen, sodass alle in einem Stapel ausgeführten Abfragen dieselbe Verbindung verwenden und somit in einer einzigen Transaktion verpackt werden können?

Die abgeschnittene Strategie funktioniert, ist aber sehr brutal. Ich habe derzeit einen test.after.always () - Hook in jeder Testdatei, der die von den Tests in dieser Datei betroffenen Tabellen abschneidet (normalerweise eine Tabelle pro Datei), aber ich kann mir Randfälle vorstellen, die dadurch durcheinander geraten würden.

Wenn beispielsweise zwei Tests aus verschiedenen Testdateien, die dieselbe Tabelle berühren, ungefähr zur gleichen Zeit ausgeführt werden, wird möglicherweise der Kürzungshaken einer Datei gestartet, während die Tests der zweiten Datei gerade ausgeführt werden, wodurch dieser Test durcheinander gebracht wird.

Schließlich ist mir noch unklar, wie Transaktionen in Knex genau funktionieren. Wenn ich mit knex.transaction() ein trx erstelle, werden alle Abfragen, die mit trx automatisch Teil der Transaktion sein? Muss ich das Commit / Rollback manuell durchführen? (Vorausgesetzt, es werden keine Fehler ausgelöst.)

Gibt es keine Möglichkeit, eine Verbindungsaffinität für die Dauer der Transaktion zu erreichen, sodass alle in einem Stapel ausgeführten Abfragen dieselbe Verbindung verwenden und somit in einer einzigen Transaktion verpackt werden können?

Ich habe das nicht verstanden

Die abgeschnittene Strategie funktioniert, ist aber sehr brutal. Ich habe derzeit einen test.after.always () - Hook in jeder Testdatei, der die von den Tests in dieser Datei betroffenen Tabellen abschneidet (normalerweise eine Tabelle pro Datei), aber ich kann mir Randfälle vorstellen, die dadurch durcheinander geraten würden.

Wenn beispielsweise zwei Tests aus verschiedenen Testdateien, die dieselbe Tabelle berühren, ungefähr zur gleichen Zeit ausgeführt werden, wird möglicherweise der Kürzungshaken einer Datei gestartet, während die Tests der zweiten Datei gerade ausgeführt werden, wodurch dieser Test durcheinander gebracht wird.

Selbst wenn Sie Transaktionen zum Zurücksetzen Ihrer Testdaten verwenden, können Sie im Allgemeinen nicht mehrere Tests parallel ausführen. ID-Sequenzen sind unterschiedlich und Transaktionen können blockieren usw.

Schließlich ist mir noch unklar, wie Transaktionen in Knex genau funktionieren. Wenn ich mit knex.transaction () einen trx erstelle, werden alle mit trx ausgeführten Abfragen automatisch Teil der Transaktion sein? Muss ich das Commit / Rollback manuell durchführen? (Vorausgesetzt, es werden keine Fehler ausgelöst.)

Dieser ist der Dokumentation zu entnehmen. Wenn Sie das Versprechen vom Rückruf in knex.transaction(callback) , müssen Sie das Commit / Rollback manuell durchführen. Wenn der Rückruf ein Versprechen zurückgibt, wird das Festschreiben / Zurücksetzen automatisch aufgerufen. In Ihrem Fall müssen Sie wahrscheinlich manuell ein Rollback durchführen.

Selbst wenn Sie Transaktionen zum Zurücksetzen Ihrer Testdaten verwenden, können Sie im Allgemeinen nicht mehrere Tests parallel ausführen. ID-Sequenzen sind unterschiedlich und Transaktionen können blockieren usw.

Ich generiere zufällig IDs für jeden Datensatz, den ich einfüge. Kollisionen sind möglich, aber unwahrscheinlich. Wenn sie einmal am Tag auftreten, ist dies nur ein Test - kein kundenorientierter Code.

Bei der Parallelität gehe ich davon aus, dass alle Abfragen, die in einer einzigen Transaktion zusammengefasst werden müssen, auch dieselbe DB-Verbindung verwenden müssen, was ich erreichen kann, indem ich meine Knex-Poolgröße auf 1 setze. Dann müsste ich die Tests in a durchführen Dateiserie mit dem Modifikator .serial . Ich hätte jedoch immer noch Parallelität zwischen Testdateien (was der wichtigste Faktor für die Leistung ist), da Ava jede Testdatei in einem separaten untergeordneten Prozess ausführt.

Ich denke, ich sollte es einfach versuchen.

Ich habe es geschafft!

https://gist.github.com/odigity/7f37077de74964051d45c4ca80ec3250

Ich verwende Ava für Unit-Tests in meinem Projekt, aber für dieses Beispiel habe ich gerade ein nachgemachtes Unit-Test-Szenario mit Vorher / Nachher-Hooks erstellt, um das Konzept zu demonstrieren.

Vor dem Haken

  • Da das Abrufen einer Transaktion eine asynchrone Aktion ist, erstelle ich ein neues Versprechen, vom before -Hook zurückzukehren und die resolve -Methode zu erfassen, damit sie im transaction() -Rückruf aufgerufen werden kann.
  • Ich öffne eine Transaktion. Im Rückruf habe ich ...

    • Speichern Sie das tx -Handle an einem Ort, auf den der Test zugreifen kann, und after hook

    • Rufen Sie die gespeicherte resolve -Methode auf, um das "Test-Framework" darüber zu informieren, dass der before -Haken abgeschlossen wurde und der Test beginnen kann

Test - Ich führe zwei Einfüge-Abfragen mit dem gespeicherten tx -Handle aus.

Nach dem Hook - Ich verwende das gespeicherte tx Handle, um die Transaktion zurückzusetzen und so alle während des Tests vorgenommenen Änderungen rückgängig zu machen. Da die Daten niemals festgeschrieben werden, können sie nicht einmal von den Aktivitäten anderer Tests gesehen werden oder diese beeinträchtigen.

Bei Parallelität

Knex

Mit Knex können Sie Optionen für die Größe des Verbindungspools angeben. Wenn Sie sicherstellen müssen, dass alle Abfragen in einer Transaktion auf derselben Verbindung ausgeführt werden (ich nehme an, dass dies der Fall ist), können Sie dies erreichen, indem Sie die Poolgröße auf 1 setzen.

_Aber warte ..._ schau dir diesen Kommentar in der Quelle an :

// We need to make a client object which always acquires the same
// connection and does not release back into the pool.
function makeTxClient(trx, client, connection) {

Wenn ich das richtig verstehe, bedeutet dies, dass man sich auf Knex verlassen kann, um sicherzustellen, dass alle Abfragen in einer Transaktion dieselbe Verbindung durchlaufen, auch wenn Ihr Pool größer als 1 ist! Wir müssen diesen besonderen Grad an Parallelität also nicht für unser Szenario opfern.

Übrigens - Die Dokumente sollten wahrscheinlich diese wesentliche und wunderbare Tatsache widerspiegeln.

Ava

_Ich kenne dieses Ava-spezifische und nicht das Knex-spezifische, aber es ist ein beliebtes Framework, und die Lektionen sind weitgehend auf die meisten Frameworks anwendbar._

Ava führt jede Testdatei gleichzeitig in einem separaten Prozess aus. Innerhalb jedes Prozesses werden auch alle Tests gleichzeitig ausgeführt. Beide Formen der Parallelität können mithilfe der CLI-Option --serial (die alles serialisiert) oder des Modifikators .serial für die Methode test (die die markierten Tests serialisiert, bevor der Rest gleichzeitig ausgeführt wird) deaktiviert werden ).

Einpacken

Wenn ich all diese Fakten zusammenstelle:

  • Ich kann jeden Test in eine Transaktion einschließen, die (a) keine Kollision von Testdaten (b) automatische Bereinigung nach dem Test ohne Abschneiden oder Remigration sicherstellt. Mit dieser Strategie wird in meiner Test-DB buchstäblich nie ein einziger Datensatz gespeichert, außer vielleicht wesentlichen Startdaten.

  • Ich kann weiterhin von Avas Parallelität zwischen Testdateien profitieren, da jede Testdatei in einem separaten Prozess ausgeführt wird und daher über einen eigenen Knex-Verbindungspool verfügt.

  • Ich kann weiterhin von der Parallelität zwischen den Testdateien von Ava profitieren, wenn ich sicherstelle, dass mein Verbindungspool> = die Anzahl der Tests in der Datei ist. Dies sollte kein Problem sein. Ich habe nicht viele Tests in einer einzigen Datei gespeichert, die ich sowieso zu vermeiden versuche. Außerdem kann der Pool mit einem Bereich von 1 bis 10 festgelegt werden, sodass Sie keine nicht benötigten Verbindungen erstellen, sondern nicht jedes Mal, wenn Sie einen Test hinzufügen oder entfernen, eine Konstante in jeder Testdatei anpassen müssen.


Eifrig, die Gedanken anderer Leute zu bekommen, während ich mich an der Schnittstelle einer großen Anzahl von Technologien versuche, die für mich gleichzeitig neu sind ...

@odigity yup, die Transaktion in knex ist so ziemlich nur ein Handle für eine dedizierte Verbindung, bei der knex beim Erstellen einer trx-Instanz automatisch eine Abfrage BEGIN hinzufügt (tatsächlich handelt es sich bei einer Transaktion in SQL im Allgemeinen nur um Abfragen an dieselbe Verbindung, der BEGIN vorangestellt ist).

Wenn knex keine Verbindung für die Transaktion zuweisen würde, würden Transaktionen überhaupt nicht funktionieren.

Die Verwendung von UUID-Schlüsseln ist ebenfalls nicht schlecht. Eine Änderung der Kollision ist nur dann wirklich unwahrscheinlich, wenn wirklich viele Zeilen vorhanden sind. ("Wenn Sie eine 128-Bit-UUID verwenden, sagt uns der 'Geburtstagseffekt', dass eine Kollision wahrscheinlich ist, nachdem Sie ungefähr 2 ^ 64 Schlüssel generiert haben, vorausgesetzt, Sie haben 128 Entropiebits in jedem Schlüssel.")

Ich nehme an, Sie haben Ihre Antworten herausgefunden, also schließen Sie diese 👍

Es tut mir leid, wieder zu öffnen, aber ich beobachte jetzt unerwartetes Verhalten. Insbesondere funktioniert es, wenn es nicht sein sollte, was bedeutet, dass mein Verständnis korrigiert werden muss.

Wenn ich die Poolgröße auf 1 setze, eine Transaktion öffne und dann eine Abfrage ohne Verwendung ausführe, tritt eine Zeitüberschreitung auf, da keine Verbindung hergestellt werden kann. Dies ist sinnvoll, da diese Verbindung bereits von der Transaktion gesperrt wurde.

Wenn ich jedoch die Poolgröße auf 1 setze und vier Tests gleichzeitig durchführe, von denen jeder:

  • öffnet eine Transaktion
  • führt Abfragen aus
  • ruft am Ende Rollback auf

Sie alle arbeiten. Sie sollten nicht funktionieren - zumindest einige von ihnen sollten durch Verbindungsknappheit ausgesperrt werden -, aber sie funktionieren jedes Mal gut.

Verfügt Knex über eine integrierte Warteschlange für Abfragen, wenn zu diesem Zeitpunkt keine Verbindungen verfügbar sind? Wenn ja, warum schlägt mein erstes Beispiel fehl, anstatt auf den Abschluss der Transaktion zu warten, bevor die Nicht-TX-Abfrage ausgeführt wird?

Oder multiplext Knex irgendwie mehrere gleichzeitige Transaktionen auf eine einzige Verbindung?

Oder erstellt Knex beim 2., 3. und 4. Aufruf tatsächlich Untertransaktionen? Wenn ja, wird dies wahrscheinlich zufällig zu erwarteten Ergebnissen führen ...

@odgity Im ersten Fall haben Sie einen anwendungsseitigen Deadlock (Ihre App wartet nicht mehr auf die Verbindung und löst Timeout-Trigger für den Erwerb der Verbindung aus).

Im zweiten Fall werden die Tests tatsächlich seriell ausgeführt, da alle bis auf einen auf die Verbindung warten. Wenn Sie genügend Tests parallel durchführen oder Ihre Testfälle sehr langsam sind, sollte purchaseConnectionTimeout auch im zweiten Fall ausgelöst werden.

Multiplexing-Transaktionen in einer einzigen Verbindung sind nicht möglich. Das Verschachteln von Transaktionen wird von knex mithilfe von Sicherungspunkten innerhalb der Transaktion unterstützt. Wenn Sie nach einer neuen Transaktion aus dem Pool fragen, wird dies auch nicht passieren.

Danke, das ist wirklich hilfreich.

Ich bin zwar immer noch ein wenig verwirrt darüber, warum das Ausführen einer Abfrage gesperrt wird, wenn die Verbindung von einer geöffneten Transaktion hergestellt wird, aber der Versuch, eine zweite neue Transaktion zu öffnen, wartet geduldig und wird dann abgeschlossen.

Führen Sie Ihren Code mit der Umgebungsvariablen DEBUG = knex: * aus, und Sie werden sehen, was der Pool tut. Knex sollte auch im ersten Fall warten, bis das Timeout "Ich konnte keine Verbindung herstellen" auftritt. Wenn Ihre Transaktion also wartet, bis die zweite Verbindung hergestellt ist, handelt es sich um einen Deadlock auf Anwendungsebene, da beide Verbindungen aufeinander warten (ich weiß jedoch nicht, ob dies der Fall ist).

Ich fand diesen Thread sehr nützlich. Es ist schön zu sehen, dass es auch anderen Leuten wichtig ist, Tests zu schreiben, die den Systemstatus nicht verschmutzen. Ich habe ein Projekt initiiert, mit dem Benutzer Tests für Code schreiben können, der knex verwendet und der nach Abschluss des Tests zurückgesetzt wird.

https://github.com/bas080/knest

@ bas080 Im Allgemeinen schränkt dieser Ansatz die Dinge ein, die Sie testen können (so dass der Test nur eine einzige Transaktion verwenden kann). Dadurch wird verhindert, dass Code getestet wird, der mehrere Verbindungen / gleichzeitige Transaktionen verwendet. Außerdem kann man auf diese Weise keinen Code testen, der implizite Commits ausführt (allerdings nicht sehr häufig).

Ich weiß, dass die Verwendung von Transaktionen zum Zurücksetzen des Status nach dem Ausführen des Tests ein weit verbreitetes Muster ist. Ich möchte betonen, dass die Verwendung nur dieses Ansatzes verhindert, dass bestimmte Dinge getestet werden.

Ich habe es immer vorgezogen, DB nach jedem Test, der Daten ändert, oder nach einer Reihe von Tests, die voneinander abhängig sind, abzuschneiden und neu zu füllen (manchmal mache ich dies aus Leistungsgründen, wenn das Auffüllen zu lange dauert, wie über 50 ms).

Hallo, es tut mir leid, dieses Problem erneut zu lösen, aber ich habe ein neues Projekt gestartet, bei dem knex mehrere Datenbanken mit gefälschten Daten durchsuchen kann. Ich möchte, dass Sie es sehen und mir mit einem Beitrag helfen

Für Unit-Tests habe ich nur überprüft, ob die von Knex aus meinem SQL-Wrapper ausgeführten Abfragen korrekt erstellt wurden, indem ich toString() am Ende der Abfragekette verwendet habe. Für Integrationstests habe ich vor jedem Test die oben bereits aufgeführte Strategie angewendet: Rollback -> Migrieren -> Seed. Verständlicherweise kann dies zu langsam sein, wenn Sie Ihre Startdaten nicht klein halten können, aber möglicherweise für andere geeignet sind.

Das heißt: Rollback -> Migrieren -> Seed vor jedem Test.

Das ist wirklich ein schlechter Weg. Das Hin- und Herführen von Migrationen dauert leicht Hunderte von Millisekunden, was viel zu langsam ist. Sie sollten Migrationen nur einmal für die gesamte Testsuite ausführen und dann vor dem Test einfach alle Tabellen abschneiden und geeignete Testdaten einfügen. Dies kann problemlos 100-mal schneller durchgeführt werden als das Zurücksetzen / Neuerstellen des gesamten Schemas.

Sie können Knex-Cleaner verwenden, um alle Tabellen einfach

knexCleaner
    .clean(knex, { ignoreTables: ['knex_migrations', 'knex_migrations_lock'] })
    .then(() => knex.seed.run())

Beachten Sie, dass Sie den Teil ignoreTables nicht verwenden müssen, wenn Sie zu Beginn jeder Testsuite Migrationen ausführen. Dies ist nur erforderlich, wenn Sie Migrationen manuell in Ihrer Testdatenbank ausführen.

@ricardograca Behandelt es Fälle mit Fremdschlüsseln gut? (Dies bedeutet, dass die Reinigung nicht fehlschlägt, da die Löschreihenfolge falsch ist.)

Wenn nicht, ist das einfach zu beheben (Sie müssen nur alle Tabellen mit einer einzigen Abfrage abschneiden) :)

@elhigu Wie kannst du das machen?

@kibertoad Ja, es behandelt Fremdschlüsseleinschränkungen

@odigity was ist, wenn wir was Struktur so zu testen:

// controller.js
const users = require('./usersModel.js');

module.exports.addUser = async ({ token, user }) => {
  // check token, or other logic
  return users.add(user);
};

// usersModel.js
const db = require('./db');

module.exports.add = async user => db('users').insert(user);

// db.js
module.exports = require('knex')({ /* config */});

Auf Ihre Weise müssen alle Funktionen ein zusätzliches Argument haben (zum Beispiel trx ), um die Transaktion an die tatsächlichen Abfrage-Builder zu übergeben.
https://knexjs.org/#Builder -transacting

Ich denke, der richtige Weg muss so etwas sein:

  1. Erstellen Sie trx in beforeEach .
  2. Spritzen Sie es irgendwie. In meinem Beispiel sollte require('./db') den trx-Wert zurückgeben.
  3. Mach Tests.
  4. Rollback trx in 'afterEach'.

Aber ich weiß nicht, ob es mit Knex möglich ist?
Was ist, wenn Code andere Transaktionen verwendet?

Auch eine andere Option: Vielleicht gibt es eine Funktion, die die Ausführung der Abfrage startet. Wir können es also überschreiben, um die Ausführung in der Testtransaktion zu erzwingen.

Nachdem ich einen Tag lang Knex-Code gelesen habe, versuche ich diesen Ansatz:

test.beforeEach(async t => {
    // if we use new 0.17 knex api knex.transaction().then - we can not handle rollback error
    // so we need to do it in old way
    // and add empty .catch to prevent unhandled rejection
    t.context.trx = await new Promise(resolve => db.transaction(trx => resolve(trx)).catch(() => {}));
    t.context.oldRunner = db.client.runner;
    db.client.runner = function(builder) {
        return t.context.oldRunner.call(t.context.trx.client, builder);
    };
    t.context.oldRaw = db.raw;
    db.raw = function(...args) {
        return t.context.oldRaw.call(this, ...args).transacting(t.context.trx);
    };
});

test.afterEach(async t => {
    db.raw = t.context.oldRaw;
    db.client.runner = t.context.oldRunner;
    await t.context.trx.rollback();
});

Und es funktioniert irgendwie. Also überschreibe ich die Methoden .raw und .client.runner . .client.runner ruft intern auf, wenn Sie .then einen Abfrage-Builder verwenden. db in dieser Funktion ist der Knex-Client knex({ /* config */}) .

@Niklv wie ich hier schon erklärt habe; Die Verwendung von Transaktionen zum Zurücksetzen von Testdaten ist im Allgemeinen eine schlechte Idee, und ich würde sie sogar als Anti-Pattern betrachten. So ist das Überschreiben von Knex-Interna. Es wird empfohlen, die Datenbank bei jedem Test oder für eine Reihe von Tests abzuschneiden + neu zu füllen. Es dauert nicht viele Millisekunden, bis die Testdaten eine angemessene Größe haben.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen