web-dev-qa-db-fra.com

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

J'utilise Node.js avec felixge node-mysql client. Je n'utilise pas d'ORM.

Je teste avec Vows et je veux pouvoir me moquer de ma base de données, peut-être en utilisant Sinon. Comme je n'ai pas vraiment de DAL en soi (à part node-mysql), Je ne sais pas trop comment faire. Mes modèles sont pour la plupart de simples CRUD avec beaucoup de getters.

Des idées sur la façon d'accomplir cela?

42
Josh Smith

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 les entrées que vous attendez. rows est la sortie que vous attendez.

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

Dans votre section d'attente de test, vous devriez avoir:

mock.verify()

et dans votre démontage, vous devez restaurer mysql à sa fonctionnalité normale:

mock.restore()
36
kgilpin

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 l'instance de cette classe aux constructeurs de votre modèle au lieu de les charger à l'aide de require ().

Avec cette configuration, vous pouvez passer une instance de base de données factice à vos modèles dans vos fichiers de test unitaire.

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});
9
Bijou Trouvaille

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

var DataContainer = function () {
}

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

Maintenant, dans le contexte d'un test, corrigez votre classe getAllBooks lors de l'initialisation comme:

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

Lorsque le code de test est appelé, getAllBooks sera remplacé par une version qui renvoie des données factices au lieu d'appeler réellement mysql. Encore une fois, ceci est un aperçu approximatif car je ne suis pas entièrement familier avec node.js

5
doogle

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

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 à la base de données, donc je me suis moqué manuellement de toutes les réponses mysql.
En ajoutant une autre fonction à .returns vous pouvez simuler n'importe quelle méthode à partir de createConnection.

5
cameck

Vous pouvez simuler les dépendances externes en utilisant horaa

Et je crois aussi que le nœud de felixge sandboxed-module peut aussi faire quelque chose de similaire.

Donc, en utilisant le même contexte de kgilpin, dans horaa, cela 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
dule

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

Il y a deux façons de procéder.

stubbing la createConnection, et lui faire retourner une connexion stubbed

Pendant l'installation:

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 qu'ici la méthode query est simulée sur une instance, et n'a aucune implication sur le mécanisme sous-jacent, donc seule la createConnection doit être restaurée.

stubbing la méthode .query sur le prototype de connexion

Cette technique est un peu plus délicate, car le pilote mysql n'expose pas officiellement sa connexion pour l'importation. (vous pouvez simplement importer uniquement le module implémentant la connexion, mais il n'y a aucune garantie qu'aucun refactoring ne le déplacera à partir de là). Donc, afin d'obtenir une référence au prototype - je crée généralement une connexion et traverse la chaîne constructeur-prototype:

Je le fais généralement en une seule ligne, mais je vais le décomposer en étapes et l'expliquer ici:

Pendant l'installation:

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'....

Pendant le démontage

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

Notez que nous ne nous moquons pas de la méthode createConnection ici. Toutes les validations des paramètres de connexion auront toujours lieu (ce que je veux qu'elles se produisent. J'aspire à travailler avec un maximum de pièces authentiques - donc se moquer du minimum absolu requis pour obtenir un test rapide). Cependant - le query se moque du prototype et doit être restauré.

Notez également que si vous travaillez chirurgicalement, le verify sera sur la méthode simulée, pas sur le mockTarget.

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

2
Radagast the Brown