Comment se moquer de MySQL(sans ORM) dans Node.js?

j'utilise Node.js avec le client node-mysql de felixge. Je ne suis pas à l'aide d'un ORM.

je teste avec des vœux et je veux pouvoir me moquer de ma base de données, peut-être en utilisant des Sinons. Comme je n'ai pas vraiment de DAL en soi (à part node-mysql ), Je ne sais pas trop comment m'y prendre. Mes modèles sont surtout de la merde simple avec beaucoup de getters.

des idées sur la façon d'accomplir ceci?

39
demandé sur Josh Smith 2011-12-05 21:16:34

6 réponses

avec sinon, vous pouvez mettre une maquette ou un talon autour d'un module entier. Par exemple, supposons que le module mysql ait une fonction query :

var mock;

mock = sinon.mock(require('mysql'))
mock.expects('query').with(queryString, queryParams).yields(null, rows);

queryString , queryParams sont l'entrée que vous attendez. rows est la sortie que vous attendez.

lorsque votre classe sous test nécessite maintenant mysql et appelle la méthode query , elle sera interceptée et vérifiée par sinon.

dans votre test section attente vous devriez avoir:

mock.verify()

et dans votre démontage vous devez restaurer mysql retour à la fonctionnalité normale:

mock.restore()
32
répondu kgilpin 2012-04-12 13:19:07

ce peut être une bonne idée d'abstraire votre base de données dans sa propre classe qui utilise mysql. Ensuite, vous pouvez passer cette instance de classe' aux constructeurs de votre model au lieu de les charger en utilisant require().

avec cette configuration, vous pouvez passer une instance de simulation de base de données à vos modèles à l'intérieur de vos fichiers de test unitaires.

voici un petit exemple:

// db.js
var Db = function() {
   this.driver = require('mysql');
};
Db.prototype.query = function(sql, callback) {
   this.driver... callback (err, results);
}
module.exports = Db;

// someModel.js
var SomeModel = function (params) {
   this.db = params.db
}
SomeModel.prototype.getSomeTable (params) {
   var sql = ....
   this.db.query (sql, function ( err, res ) {...}
}
module.exports = SomeModel;

// in app.js
var db = new (require('./db.js'))();
var someModel = new SomeModel ({db:db});
var otherModel = new OtherModel ({db:db})

// in app.test.js
var db = {
   query: function (sql, callback) { ... callback ({...}) }
}
var someModel = new SomeModel ({db:db});
8
répondu Bijou Trouvaille 2012-03-15 15:14:00

Je ne suis pas tout à fait familier avec node.js, mais dans un sens traditionnel de programmation, pour réaliser des tests comme celui-ci, vous devez abstraire loin de la méthode d'accès aux données. Ne pourriez-vous pas créer une classe de DAL comme:

var DataContainer = function () {
}

DataContainer.prototype.getAllBooks = function() {
    // call mysql api select methods and return results...
}

maintenant dans le contexte d'un test, patch votre classe getAllBooks pendant l'initialisation comme:

DataContainer.prototype.getAllBooks = function() {
    // Here is where you'd return your mock data in whatever format is expected.
    return [];
}

quand le code de test est appelé, getAllBooks sera remplacé par une version qui retourne des données mock au lieu de fait appel mysql. Encore une fois, il s'agit d'une vue d'ensemble car je ne suis pas entièrement familier avec node.js

5
répondu doogle 2012-01-07 23:46:43

j'ai fini par commencer avec la réponse de @kgilpin et j'ai fini avec quelque chose comme ça pour tester Mysql dans une Lambda AWS:

const sinon = require('sinon');
const LambdaTester = require('lambda-tester');
const myLambdaHandler = require( '../../lambdas/myLambda' ).handler;
const mockMysql = sinon.mock(require('mysql'));
const chai = require('chai');
const expect = chai.expect;

describe('Database Write Requests', function() {

 beforeEach(() => {
   mockMysql.expects('createConnection').returns({
     connect: () => {
       console.log('Succesfully connected');
     },
     query: (query, vars, callback) => {
       callback(null, succesfulDbInsert);
     },
     end: () => {
       console.log('Connection ended');
     }
   });

 });
 after(() => {
   mockMysql.restore();
 });

 describe( 'A call to write to the Database with correct schema', function() {

   it( 'results in a write success', function() {

     return LambdaTester(myLambdaHandler)
       .event(anObject)
       .expectResult((result) => {
         expect(result).to.equal(succesfulDbInsert);
       });
   });
 });


 describe( 'database errors', function() {

   before(() => {
     mockMysql.expects('createConnection').returns({
       connect: () => {
         console.log('Succesfully connected');
       },
       query: (query, vars, callback) => {
         callback('Database error!', null);
       },
       end: () => {
         console.log('Connection ended');
       }
     });
   });

   after(() => {
     mockMysql.restore();
   });

   it( 'results in a callback error response', function() {


     return LambdaTester(myLambdaHandler)
       .event(anObject)
       .expectError((err) => {
         expect(err.message).to.equal('Something went wrong');
       });
   });
 });
});

Je ne voulais pas de connexions réelles dans la base de données, alors j'ai tourné en dérision toutes les réponses mysql.

En ajoutant une autre fonction à .returns vous pouvez moquer n'importe quelle méthode hors de createConnection .

5
répondu cameck 2017-06-12 21:25:12

vous pouvez supprimer les dépendances externes en utilisant horaa

et je crois aussi que le noeud de felixge sandboxed-module peut aussi faire quelque chose de similaire.

donc en utilisant le même contexte de kgilpin, dans horaa ça ressemblerait à quelque chose comme:

var mock = horaa('mysql');
mock.hijack('query', function(queryString, queryParam) {
    // do your fake db query (e.g., return fake expected data)
});

//SUT calls and asserts

mock.restore('query');
3
répondu dule 2012-06-05 21:46:33

puisque l'utilisation du pilote mysql nécessite d'abord de créer une connexion, et d'utiliser les API du contrôleur de connexion retourné - vous avez besoin d'une approche en deux étapes.

Il y a deux façons de le faire.

buter le createConnection, et le retour d'un écrasé de connexion

pendant la configuration:

const sinon = require('sinon');
const mysql = require('mysql');
const {createConnection} = mysql;
let mockConnection;
sinon.stub(mysql, 'createConnection').callsFake((...args) => {
    mockConnection = sinon.stub(createConnection.apply(mysql, args))
      .expects('query').withArgs(.... )//program it how you like :)
    return mockConnection;
})

const mockConnectionFactory = 
  sinon.stub(mysql)
  .expects('createConnection')

Pendant Le Démontage:

mysql.createConnection.restore();

notez que le La méthode query est moquée sur une instance, et n'a aucune implication sur le mécahnisme sous-jacent, de sorte que seul le createConnection doit être restauré.

buter le .méthode d'interrogation sur le prototype de connexion

cette technique est un peu plus délicate, car le pilote mysql n'expose pas officiellement sa connexion pour importation. (Eh bien, vous pouvez juste importer le module implémentant la connexion, mais il n'y a aucune garantie que n'importe quel refactoring ne bougera pas de là). Donc, pour obtenir une référence au prototype - je crée habituellement une connexion et traverse la chaîne constructeur-prototype:

je le fais habituellement en une ligne, mais je vais le décomposer en étapes et l'expliquer ici:

pendant la configuration:

const realConnection = mysql.createConnection({})
const mockTarget = realConnection.constructor.prototype;
//Then - brutally
consdt mock = sinon.mock(mockTarget).expect('query'....
//OR - as I prefer the surgical manner
sinon.stub(mockTarget, 'query').expect('query'....

Lors De La Mise À Nu

//brutal
mock.restore()
// - OR - surgical:
mockTarget.query.restore()

notez que nous ne nous moquons pas de la méthode createConnection ici. Tous les connexion paramètre validations arrivera encore (je veux qu'ils se produisent. J'aspire à travailler avec le maximum de pièces authentiques - donc mock le minimum absolu requis pour obtenir un test rapide). Cependant - le query est moqué sur le prototype, et doit être restauré.

aussi noter que si vous travaillez chirurgicalement, le verify sera sur la méthode mocktarget, pas sur la méthode mockTarget.

Voici une bonne ressource à ce sujet: http://devdocs.io/sinon~6-stubs /

0
répondu Radagast the Brown 2018-09-13 08:33:26