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?
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()
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});
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
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
.
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');
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 /