Knex: Comment écrire des tests unitaires pour les méthodes qui utilisent Knex.

CrĂ©Ă© le 21 mai 2017  Â·  28Commentaires  Â·  Source: knex/knex

(Publié à l'origine dans # 1659, déplacé ici pour plus de discussion.)

Je suis un peu dans le noir sur celui-ci - les documents knex couvrent le strict minimum des transactions, mais lorsque je google "en utilisant knex.js pour les tests unitaires" et "ava.js + knex" et "ava.js + base de donnĂ©es" , Je n'ai rien trouvĂ© de particuliĂšrement instructif pour me montrer un bon moyen, alors je me suis rabattu sur mon expĂ©rience Ruby oĂč envelopper un test unitaire dans une transaction est une technique courante pour rĂ©initialiser la base de donnĂ©es aprĂšs chaque test.

@odigity Je ne suggérerais pas de faire cela parce que vos tests finiront par n'utiliser qu'une seule connexion au lieu d'un pool de connexions et cela ne représentera pas l'utilisation réelle de votre application.

Cela m'est venu Ă  l'esprit, mais j'Ă©tais prĂȘt Ă  l'accepter si cela me permettait d'utiliser une mĂ©thode simple et Ă  Ă©criture unique pour implĂ©menter le nettoyage de la base de donnĂ©es post-test. (J'ai dĂ©fini mon pool min / max sur 1 pour ĂȘtre sĂ»r.) S'il existe un moyen d'accomplir cela tout en prenant en charge les connexions simultanĂ©es, je l'adopterai absolument.

De plus, il est quasiment impossible Ă  mettre en Ɠuvre Ă  moins que l'application que vous testez n'exĂ©cute ses requĂȘtes dans la mĂȘme transaction qui a Ă©tĂ© lancĂ©e dans le code de test (vous devrez dĂ©marrer la transaction dans le test, puis dĂ©marrer votre application et transmettre la transaction crĂ©Ă©e Ă  l'application afin qu'elle effectue toutes les requĂȘtes vers la mĂȘme transaction et les transactions imbriquĂ©es peuvent se comporter de maniĂšre stricte ...).

La façon dont j'espérais / m'attendais à ce que cela fonctionne est:

1) Dans chaque test unitaire, je crée une transaction qui produit trx .

2) J'exige ensuite le module que je veux tester et passe l'objet trx au constructeur du module afin qu'il soit utilisĂ© par le module, ce qui entraĂźnera toutes les requĂȘtes Ă  l'intĂ©rieur de la transaction.

3) Une fois que la méthode module a renvoyé (ou a généré une erreur), j'exécute mes assertions sur l'état résultant de la base de données, puis j'appelle trx.rollback() pour tout annuler depuis le début pour préparer le test suivant.

C'est donc ce que j'essaie de réaliser et comment j'avais initialement l'intention de le réaliser. J'ai hùte d'en savoir plus sur:

1) En quoi je ne comprends pas comment Knex.js fonctionne et devrait ĂȘtre utilisĂ©.

2) Meilleures pratiques pour écrire des tests unitaires atomiques pour le code qui touche la base de données.

question

Commentaire le plus utile

Non - rien obtenu. Résumé par ordre chronologique des sources:

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

StratĂ©gie: utilisez mock-knex . Je ne veux pas me moquer de la base de donnĂ©es - je veux tester mes mĂ©thodes par rapport Ă  une base de donnĂ©es MySQL rĂ©elle pour garantir un comportement correct - mais j'ai quand mĂȘme jetĂ© un coup d'Ɠil Ă  mock-knex ... c'est peut-ĂȘtre la bibliothĂšque la moins bien conçue Je l'ai jamais rencontrĂ©. :(

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

StratĂ©gie: Rollback / remigrate / reseed DB aprĂšs chaque test. Cela semble ĂȘtre une surcharge pour chaque test, et sera terriblement lent. La concurrence pourrait ĂȘtre obtenue en gĂ©nĂ©rant un UUID pour le nom de la base de donnĂ©es de chaque test, mais cela semble ĂȘtre une solution horrible par rapport Ă  l'Ă©lĂ©gance des transactions ...

23/09/2015 http://stackoverflow.com/a/32749601/210867

Stratégie: utilisez sqlite pour les tests, créez / détruisez DB pour chaque test. J'ai couvert les deux raisons pour lesquelles je n'aime pas cette approche ci-dessus.

Donc ... toujours à la recherche de suggestions et de conseils supplémentaires sur le fonctionnement réel des transactions Knex, ainsi que sur la maniÚre de les appliquer correctement à mon cas d'utilisation.

Tous les 28 commentaires

J'ai apparemment fait un mauvais travail de recherche sur Google la nuit derniÚre, parce que je viens de réessayer avec "comment écrire des tests unitaires atomiques avec knex.js", et j'obtiens des résultats intéressants. Je vais les lire maintenant. Je publierai à nouveau si j'apprends quelque chose.

Non - rien obtenu. Résumé par ordre chronologique des sources:

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

StratĂ©gie: utilisez mock-knex . Je ne veux pas me moquer de la base de donnĂ©es - je veux tester mes mĂ©thodes par rapport Ă  une base de donnĂ©es MySQL rĂ©elle pour garantir un comportement correct - mais j'ai quand mĂȘme jetĂ© un coup d'Ɠil Ă  mock-knex ... c'est peut-ĂȘtre la bibliothĂšque la moins bien conçue Je l'ai jamais rencontrĂ©. :(

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

StratĂ©gie: Rollback / remigrate / reseed DB aprĂšs chaque test. Cela semble ĂȘtre une surcharge pour chaque test, et sera terriblement lent. La concurrence pourrait ĂȘtre obtenue en gĂ©nĂ©rant un UUID pour le nom de la base de donnĂ©es de chaque test, mais cela semble ĂȘtre une solution horrible par rapport Ă  l'Ă©lĂ©gance des transactions ...

23/09/2015 http://stackoverflow.com/a/32749601/210867

Stratégie: utilisez sqlite pour les tests, créez / détruisez DB pour chaque test. J'ai couvert les deux raisons pour lesquelles je n'aime pas cette approche ci-dessus.

Donc ... toujours à la recherche de suggestions et de conseils supplémentaires sur le fonctionnement réel des transactions Knex, ainsi que sur la maniÚre de les appliquer correctement à mon cas d'utilisation.

@odigity a bien rĂ©sumĂ© les mauvaises pratiques de test 👍

Nous faisons nos tests "unitaires" comme ceci:

  1. Démarrez le systÚme, initialisez la base de données et exécutez les migrations

  2. Avant chaque test, nous tronquons toutes les tables et séquences (avec le package knex-db-manager)

  3. Insérer les données requises pour le cas de test (nous utilisons l'ORM d'objection.js basé sur knex pour ce qui nous permet d'insérer des hiérarchies d'objets imbriqués avec une seule commande, il sait comment optimiser les insertions afin qu'il n'ait pas à faire d'insertion séparée pour chaque ligne dans le tableau, mais généralement un seul insert par tableau)

  4. exécuter 1 test et passer à l'étape 2

Avec les tests e2e, nous avons implémenté les méthodes saveState / restoreState (avec pg_restore / pg_dump), ce qui nous permet de revenir à un certain état pendant le test, nous n'avons donc pas à redémarrer le test à chaque fois qu'un test échoue aprÚs l'exécution de 20 minutes de tests.

C'est similaire à ce que j'ai commencé à faire hier parce que c'est simple et direct et que j'avais besoin de progresser.

Avez-vous envisagĂ© la stratĂ©gie d'encapsuler les tests dans les transactions et de revenir en arriĂšre comme une alternative plus rapide Ă  tronquer toutes les tables? Cela me semble ĂȘtre l'idĂ©al, si je peux comprendre l'implĂ©mentation.

Je suppose que si vous exĂ©cutez db et le code d'application dans le mĂȘme processus, vous pouvez crĂ©er une transaction dans le code de test, puis enregistrer cette transaction comme votre instance knex (l'instance et la transaction knex semblent un peu diffĂ©rentes dans certains cas, mais vous pouvez gĂ©nĂ©ralement utiliser transaction instance comme une instance knex normale).

Ensuite, vous dĂ©marrez le code de votre application, qui rĂ©cupĂšre la transaction au lieu de l'instance knex groupĂ©e normale et commencez Ă  effectuer des requĂȘtes Ă  travers cela.

À peu prĂšs de la mĂȘme maniĂšre que vous avez dĂ©crit dans OP. Comment vous avez dĂ©crit cela semble viable.

J'ai envisagĂ© d'utiliser des transactions pour rĂ©initialiser la base de donnĂ©es dans les tests il y a quelques annĂ©es, mais je l'ai rejetĂ© parce que je veux que le pool de connexions fonctionne dans les tests Ă  peu prĂšs de la mĂȘme maniĂšre que cela fonctionne dans app + truncate / init est assez rapide pour nous.

N'existe-t-il aucun moyen d'obtenir une affinitĂ© de connexion pour la durĂ©e de la transaction de sorte que toutes les requĂȘtes exĂ©cutĂ©es dans un lot utilisent la mĂȘme connexion et soient donc encapsulables dans une seule transaction?

La stratégie tronquée fonctionne, mais c'est une force trÚs brute. J'ai actuellement un crochet test.after.always () dans chaque fichier de test qui tronque les tables affectées par les tests de ce fichier (généralement une table par fichier), mais je peux penser aux cas marginaux qui seraient foirés par cela.

Par exemple, si deux tests de diffĂ©rents fichiers de test qui touchent la mĂȘme table s'exĂ©cutent Ă  peu prĂšs au mĂȘme moment, le crochet tronquĂ© d'un fichier peut dĂ©marrer pendant que les tests du deuxiĂšme fichier sont en cours d'exĂ©cution, ce qui fausse ce test.

Enfin, je ne sais toujours pas exactement comment les transactions fonctionnent dans Knex. Si je crĂ©e un trx avec knex.transaction() , toutes les requĂȘtes exĂ©cutĂ©es avec trx feront-elles automatiquement partie de la transaction? Dois-je valider / annuler manuellement? (En supposant qu'aucune erreur n'a Ă©tĂ© gĂ©nĂ©rĂ©e.)

N'existe-t-il aucun moyen d'obtenir une affinitĂ© de connexion pour la durĂ©e de la transaction de sorte que toutes les requĂȘtes exĂ©cutĂ©es dans un lot utilisent la mĂȘme connexion et soient donc encapsulables dans une seule transaction?

Je n'ai pas compris ça

La stratégie tronquée fonctionne, mais c'est une force trÚs brute. J'ai actuellement un crochet test.after.always () dans chaque fichier de test qui tronque les tables affectées par les tests de ce fichier (généralement une table par fichier), mais je peux penser aux cas marginaux qui seraient foirés par cela.

Par exemple, si deux tests de diffĂ©rents fichiers de test qui touchent la mĂȘme table s'exĂ©cutent Ă  peu prĂšs au mĂȘme moment, le crochet tronquĂ© d'un fichier peut dĂ©marrer pendant que les tests du deuxiĂšme fichier sont en cours d'exĂ©cution, ce qui fausse ce test.

MĂȘme si vous utilisez des transactions pour rĂ©initialiser vos donnĂ©es de test, vous ne pourrez pas exĂ©cuter plusieurs tests en parallĂšle dans le cas gĂ©nĂ©ral. Les sĂ©quences d'identifiants seront diffĂ©rentes et les transactions peuvent se bloquer, etc.

Enfin, je ne sais toujours pas exactement comment les transactions fonctionnent dans Knex. Si je crĂ©e un trx avec knex.transaction (), toutes les requĂȘtes exĂ©cutĂ©es Ă  l'aide de trx feront-elles automatiquement partie de la transaction? Dois-je valider / annuler manuellement? (En supposant qu'aucune erreur n'a Ă©tĂ© gĂ©nĂ©rĂ©e.)

Celui-ci se trouve dans la documentation. Si vous ne renvoyez pas la promesse de rappel dans knex.transaction(callback) vous devez valider / annuler manuellement. Si le rappel renvoie la promesse, alors commit / rollback est appelé automatiquement. Dans votre cas, vous devrez probablement revenir en arriÚre manuellement.

MĂȘme si vous utilisez des transactions pour rĂ©initialiser vos donnĂ©es de test, vous ne pourrez pas exĂ©cuter plusieurs tests en parallĂšle dans le cas gĂ©nĂ©ral. Les sĂ©quences d'identifiants seront diffĂ©rentes et les transactions peuvent se bloquer, etc.

Je génÚre des identifiants au hasard pour chaque enregistrement que j'insÚre. Les collisions sont possibles, mais peu probables, et si cela se produit une fois un jour, ce n'est qu'un test - pas du code destiné au client.

En ce qui concerne la concurrence, je suppose que toutes les requĂȘtes qui doivent ĂȘtre regroupĂ©es en une seule transaction doivent Ă©galement utiliser la mĂȘme connexion DB, ce que je peux rĂ©aliser en dĂ©finissant la taille de mon pool Knex sur 1. Je devrais alors effectuer les tests dans un fichier serial avec le modificateur .serial . Cependant, j'aurais toujours la concurrence entre les fichiers de test (qui est le facteur le plus important pour les performances) car Ava exĂ©cute chaque fichier de test dans un processus enfant sĂ©parĂ©.

Je suppose que je devrais juste essayer.

Je l'ai fait fonctionner!

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

J'utilise Ava pour les tests unitaires dans mon projet, mais pour cet exemple, je viens de créer un scénario de test unitaire simulé avec des crochets avant / aprÚs pour démontrer le concept.

Avant le crochet

  • Parce que l'obtention d'une transaction est une action asynchrone, je crĂ©e une nouvelle promesse Ă  retourner du hook before et capture sa mĂ©thode resolve afin qu'elle puisse ĂȘtre appelĂ©e dans le rappel transaction() .
  • J'ouvre une transaction. Dans le rappel, je ...

    • enregistrer le handle tx dans un endroit qui sera accessible au test et after hook

    • appelez la mĂ©thode resolve enregistrĂ©e pour informer le "cadre de test" que le hook before est terminĂ©, et le test peut commencer

Test - J'exĂ©cute deux requĂȘtes d'insertion en utilisant le handle tx enregistrĂ©.

After Hook - J'utilise le handle tx sauvegardé

Sur la concurrence

Knex

Knex vous permet de spĂ©cifier des options pour la taille du pool de connexions. Si vous devez vous assurer que toutes les requĂȘtes d'une transaction s'exĂ©cutent sur la mĂȘme connexion (je suppose que nous le faisons), vous pouvez y parvenir en dĂ©finissant la taille du pool sur 1.

_Mais attendez ..._ consultez ce commentaire dans la source :

// 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) {

Si je comprends bien cela, cela signifie que Knex peut ĂȘtre invoquĂ© pour garantir que toutes les requĂȘtes d'une transaction passent par la mĂȘme connexion, mĂȘme si votre pool est supĂ©rieur Ă  1! Nous n'avons donc pas besoin de sacrifier ce degrĂ© particulier de concurrence pour notre scĂ©nario.

BTW - Les documents devraient probablement refléter ce fait essentiel et merveilleux.

Ava

_Je connais cela spécifique à Ava plutÎt que spécifique à Knex, mais c'est un framework populaire, et les leçons sont largement applicables à la plupart des frameworks._

Ava exécute chaque fichier de test simultanément dans un processus distinct. Dans chaque processus, il exécute également tous les tests simultanément. Les deux formes de concurrence sont désactivables à l'aide de l'option CLI --serial (qui sérialise tout) ou du modificateur .serial sur la méthode test (qui sérialise les tests marqués avant d'exécuter le reste simultanément ).

Emballer

Quand je rassemble tous ces faits:

  • Je peux envelopper chaque test dans une transaction, ce qui garantit (a) la non-collision des donnĂ©es de test (b) le nettoyage post-test automatique sans troncature ni remigration. En fait, en utilisant cette stratĂ©gie, ma base de donnĂ©es de test n'aura littĂ©ralement jamais un seul enregistrement persistant, Ă  part peut-ĂȘtre des donnĂ©es de dĂ©part essentielles.

  • Je peux continuer Ă  bĂ©nĂ©ficier de la concurrence entre les fichiers de test d'Ava, puisque chaque fichier de test s'exĂ©cute dans un processus sĂ©parĂ©, et aura donc son propre pool de connexions Knex.

  • Je peux continuer Ă  bĂ©nĂ©ficier de la concurrence intra-fichier de test d'Ava si je m'assure que mon pool de connexions est> = le nombre de tests dans le fichier. Cela ne devrait pas ĂȘtre un problĂšme.Je ne mets pas un grand nombre de tests dans un seul fichier, ce que j'essaye d'Ă©viter de toute façon. En outre, le pool peut ĂȘtre dĂ©fini avec une plage telle que 1 Ă  10, vous ne crĂ©ez donc pas de connexions dont vous n'avez pas besoin, mais vous n'avez pas Ă  ajuster une constante dans chaque fichier de test Ă  chaque fois que vous ajoutez ou supprimez un test.


DĂ©sireux de capter l'opinion des autres, car je barbote Ă  l'intersection d'un grand nombre de technologies qui sont nouvelles pour moi Ă  la fois ...

@odigity yup, la transaction dans knex n'est quasiment qu'un descripteur de connexion dĂ©diĂ©e oĂč knex ajoute automatiquement une requĂȘte BEGIN lors de la crĂ©ation d'une instance trx (en fait, la transaction en SQL est gĂ©nĂ©ralement juste des requĂȘtes allant Ă  la mĂȘme connexion prĂ©cĂ©dĂ©e de BEGIN).

Si knex n'allouait pas de connexion pour la transaction, alors les transactions ne fonctionneraient tout simplement pas.

Utiliser des clés uuid n'est pas mal non plus, le changement de collision est vraiment improbable à moins qu'il y ait vraiment beaucoup de lignes. ("Si vous utilisez un UUID de 128 bits, l '' effet anniversaire 'nous indique qu'une collision est probable aprÚs avoir généré environ 2 ^ 64 clés, à condition que vous ayez 128 bits d'entropie dans chaque clé.")

Je suppose que vous avez compris vos rĂ©ponses, alors fermez ceci 👍

DĂ©solĂ© de rouvrir, mais j'observe maintenant un comportement inattendu. Plus prĂ©cisĂ©ment, cela fonctionne quand cela ne devrait pas ĂȘtre, ce qui signifie que ma comprĂ©hension doit ĂȘtre corrigĂ©e.

Si je rĂšgle la taille du pool sur 1, ouvre une transaction, puis exĂ©cute une requĂȘte sans l'utiliser, le dĂ©lai expire car il ne peut pas obtenir de connexion - ce qui est logique, car cette connexion a dĂ©jĂ  Ă©tĂ© verrouillĂ©e par la transaction.

Cependant, si je rÚgle la taille du pool sur 1 et exécute quatre tests simultanément, chacun d'entre eux:

  • ouvre une transaction
  • exĂ©cute des requĂȘtes
  • appelle la restauration Ă  la fin

Ils fonctionnent tous. Ils ne devraient pas fonctionner - au moins quelques-uns d'entre eux devraient ĂȘtre bloquĂ©s par la raretĂ© des connexions - mais ils fonctionnent tous bien, Ă  chaque fois.

Knex dispose-t-il d'une file d'attente intĂ©grĂ©e pour les requĂȘtes lorsqu'aucune connexion n'est disponible Ă  ce moment-lĂ ? Si tel est le cas, pourquoi mon premier exemple Ă©choue-t-il, au lieu d'attendre la fin de la transaction avant d'exĂ©cuter la requĂȘte non-tx?

Ou Knex multiplexe-t-il en quelque sorte plusieurs transactions simultanées sur une seule connexion?

Ou Knex crée-t-il réellement des sous-transactions lors des 2Úme, 3Úme et 4Úme invocations? Si tel est le cas, cela entraßnera probablement au hasard les résultats attendus ...

@odgity Dans le premier cas, vous avez un blocage cÎté application (votre application est bloquée dans l'attente de la connexion et des déclencheurs de délai d'expiration de connexion).

Dans le second cas, les tests sont en fait exécutés en série, puisque tout le monde sauf un attend la connexion. Si vous avez suffisamment de tests pour le faire en parallÚle ou que vos cas de test sont vraiment lents, acquérezConnectionTimeout devrait également se déclencher dans le second cas.

Le multiplexage des transactions en connexion unique n'est pas possible. L'imbrication des transactions est prise en charge par knex Ă  l'aide de points de sauvegarde dans la transaction. Si vous demandez une nouvelle transaction Ă  partir du pool, cela ne se produira pas non plus.

Merci, c'est vraiment utile.

Bien que je sois encore un peu confus sur la raison pour laquelle l'exĂ©cution d'une requĂȘte se verrouille lorsque la connexion est prise par une transaction ouverte, mais la tentative d'ouvrir une deuxiĂšme nouvelle transaction attend patiemment, puis se termine.

Exécutez votre code avec la variable d'environnement DEBUG = knex: * et vous verrez ce que fait le pool. Knex devrait également attendre dans le premier cas jusqu'à ce que le délai "Je n'ai pas pu obtenir de connexion" se produise. Donc, si votre transaction attend jusqu'à ce que la deuxiÚme connexion soit acquise, il s'agit d'un blocage au niveau de l'application car les deux connexions attendent l'une l'autre (je ne sais pas si c'est votre cas).

J'ai trouvé ce fil trÚs utile. C'est agréable de voir que d'autres personnes se soucient également d'écrire des tests qui ne polluent pas l'état du systÚme. J'ai lancé un projet qui permet aux gens d'écrire des tests pour du code qui utilise knex qui sera restauré une fois le test terminé.

https://github.com/bas080/knest

@ bas080 en gĂ©nĂ©ral, cette approche limite les choses que vous pouvez tester (permettant au test d'utiliser une seule transaction). Cela empĂȘchera de tester le code qui utilise plusieurs connexions / transactions simultanĂ©es. On ne peut pas non plus tester le code qui effectue des validations implicites (des cas peu courants cependant) de cette façon.

Je sais que l'utilisation de la transaction pour rĂ©initialiser l'Ă©tat aprĂšs l'exĂ©cution du test est un modĂšle assez courant, ce que je veux souligner, c'est que l'utilisation de cette seule approche empĂȘche de tester certaines choses.

J'ai toujours préféré tronquer et repeupler la base de données aprÚs chaque test qui modifie les données ou aprÚs un ensemble de tests qui dépendent les uns des autres (parfois je le fais pour des raisons de performances si le peuplement prend trop de temps, comme plus de 50 ms).

salut, désolé de rouvrir ce numéro mais j'ai commencé un nouveau projet qui est un cli pour que knex _seed plusieurs bases de données avec de fausses données, _ je voudrais que vous le voyiez et m'aidiez avec une contribution

Pour les tests unitaires, j'ai seulement vĂ©rifiĂ© que les requĂȘtes exĂ©cutĂ©es par Knex Ă  partir de mon wrapper SQL sont correctement formĂ©es, en utilisant toString() Ă  la fin de la chaĂźne de requĂȘtes. Pour les tests d'intĂ©gration, j'ai utilisĂ© la stratĂ©gie dĂ©jĂ  rĂ©pertoriĂ©e ci-dessus - Ă  savoir faire un cycle: rollback -> migrate -> seed, avant chaque test. Naturellement, cela peut ĂȘtre trop lent si vous ne pouvez pas garder vos donnĂ©es d'amorçage petites, mais peut convenir Ă  d'autres.

c'est-Ă -dire faire un cycle: rollback -> migrate -> seed, avant chaque test.

C'est vraiment une mauvaise façon de le faire. L'exĂ©cution des migrations dans les deux sens prend facilement des centaines de millisecondes, ce qui est bien trop lent. Vous ne devez exĂ©cuter les migrations qu'une seule fois pour toute la suite de tests, puis avant le test, tronquez toutes les tables et remplissez les donnĂ©es de test appropriĂ©es. Cela peut ĂȘtre fait facilement 100 fois plus rapidement que de restaurer / recrĂ©er tout le schĂ©ma.

Vous pouvez utiliser knex-cleaner pour tronquer facilement toutes les tables:

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

Notez qu'il n'est pas nécessaire d'utiliser la partie ignoreTables si vous exécutez des migrations au début de chaque exécution de la suite de tests. Cela n'est nécessaire que si vous exécutez les migrations manuellement sur votre base de données de test.

@ricardograca GÚre- t-il bien les cas avec des clés étrangÚres? (ce qui signifie que le nettoyage n'échouera pas car l'ordre de suppression est erroné)

Sinon, c'est facile Ă  corriger (il suffit de tronquer toutes les tables avec une seule requĂȘte) :)

@elhigu Comment pouvez-vous faire ça?

Dépend de la base de données, mais par exemple comme ceci: https://github.com/Vincit/knex-db-manager/blob/master/lib/PostgresDatabaseManager.js#L95

@kibertoad Oui, il gÚre trÚs bien les contraintes de clé étrangÚre et supprime tout.

@odigity et si on

// 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 */});

À votre façon, toutes les fonctions doivent avoir un argument supplĂ©mentaire (par exemple trx ) pour passer la transaction aux gĂ©nĂ©rateurs de requĂȘtes rĂ©els.
https://knexjs.org/#Builder -transacting

Je pense que la bonne maniĂšre doit ĂȘtre quelque chose comme ça:

  1. Créez trx dans beforeEach .
  2. Injectez-le d'une maniĂšre ou d'une autre. Dans mon exemple, require('./db') devrait renvoyer une valeur trx.
  3. Faites des tests.
  4. Rollback trx dans 'afterEach'.

Mais, je ne sais pas est-ce possible avec knex?
Et si le code utilise d'autres transactions?

Aussi une autre option: peut-ĂȘtre qu'il y a une fonction qui commence Ă  exĂ©cuter la requĂȘte. Nous pouvons donc le remplacer pour forcer l'exĂ©cution dans la transaction de test.

AprÚs une journée de lecture du code knex, j'essaye cette approche:

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();
});

Et ça marche un peu. Donc, je remplace les mĂ©thodes .raw et .client.runner . .client.runner appelle en interne lorsque vous .then un gĂ©nĂ©rateur de requĂȘtes. db dans cette fonction est le client knex knex({ /* config */}) .

@Niklv comme je l'ai dĂ©jĂ  expliquĂ© ici; l'utilisation de la transaction pour rĂ©initialiser les donnĂ©es de test est gĂ©nĂ©ralement une mauvaise idĂ©e et je la considĂ©rerais mĂȘme comme un anti-pattern. Il en va de mĂȘme pour les internes de knex. Tronquer + repeupler la base de donnĂ©es Ă  chaque test ou pour un ensemble de tests est une recommandation. Cela ne prend pas plusieurs millisecondes si les donnĂ©es de test sont de taille raisonnable.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

ghost picture ghost  Â·  3Commentaires

fsebbah picture fsebbah  Â·  3Commentaires

npow picture npow  Â·  3Commentaires

legomind picture legomind  Â·  3Commentaires

lanceschi picture lanceschi  Â·  3Commentaires