ti-enxame.com

Como você zomba do MySQL (sem um ORM) no Node.js.

Estou a usar Node.js com felixge node-mysql cliente. Não estou usando um ORM.

Estou testando com os votos e quero zombar do meu banco de dados, possivelmente usando o Sinon. Como eu realmente não tenho um DAL em si (além de node-mysql), Não tenho muita certeza de como fazer isso. Meus modelos são na maior parte simples CRUD com muitos getters.

Alguma idéia de como fazer isso?

42
Josh Smith

Com o sinon, você pode colocar um mock ou esboço em torno de um módulo inteiro. Por exemplo, suponha que o módulo mysql tenha uma função query:

var mock;

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

queryString, queryParams são a entrada que você espera. rows é a saída que você espera.

Quando sua classe em teste agora exigir o mysql e chamar o método query, ela será interceptada e verificada pelo sinon.

Na seção de expectativa de teste, você deve ter:

mock.verify()

e na sua desmontagem você deve restaurar o mysql de volta à funcionalidade normal:

mock.restore()
36
kgilpin

Pode ser uma boa idéia abstrair seu banco de dados em sua própria classe, que usa o mysql. Em seguida, você pode passar a instância dessa classe para os construtores do seu modelo, em vez de carregá-la usando require ().

Com essa configuração, você pode transmitir uma instância db falsa para seus modelos nos arquivos de teste de unidade.

Aqui está um pequeno exemplo:

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

Não estou totalmente familiarizado com o node.js, mas, no sentido tradicional de programação, para realizar testes como esse, você precisaria abstrair-se do método de acesso a dados. Não foi possível criar uma classe DAL como:

var DataContainer = function () {
}

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

Agora, no contexto de um teste, corrija sua classe getAllBooks durante a inicialização, como:

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

Quando o código de teste é chamado, getAllBooks será substituído por uma versão que retorna dados simulados em vez de realmente chamar o mysql. Novamente, esta é uma visão geral, pois não estou totalmente familiarizado com o node.js

5
doogle

Acabei começando com a resposta do @ kgilpin e acabei com algo parecido com isto para testar o Mysql em um 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');
       });
   });
 });
});

Eu não queria conexões reais com o banco de dados, então zombei manualmente de todas as respostas do mysql.
Adicionando outra função a .returns você pode simular qualquer método fora de createConnection.

5
cameck

Você pode simular dependências externas usando horaa

E também acredito que o nó do felixge sandboxed-module também pode fazer algo semelhante.

Então, usando o mesmo contexto do kgilpin, em horaa seria algo como:

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

Como o uso do driver mysql requer que você primeiro crie uma conexão e use apis do controlador de conexão retornado - você precisa de uma abordagem em duas etapas.

Existem duas maneiras de fazer isso.

stubbing o createConnection e faça com que ele retorne uma conexão stubbed

Durante a instalação:

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

Durante a Desmontagem:

mysql.createConnection.restore();

Observe que aqui o método query é ridicularizado em uma instância e não tem implicações no mecanismo subjacente; portanto, apenas o createConnection deve ser restaurado.

stubbing o método .query no protótipo de conexão

Essa técnica é um pouco mais complicada, porque o driver mysql não expõe oficialmente sua conexão para importação. (bem, você pode importar apenas o módulo que implementa a conexão, mas não há garantia de que qualquer refatoração não a mova de lá). Portanto, para obter uma referência ao protótipo - costumo criar uma conexão e percorrer a cadeia construtor-protótipo:

Eu costumo fazer isso em uma linha, mas vou dividir em etapas e explicar aqui:

Durante a instalação:

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

Durante a Desmontagem

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

Observe que não simulamos o método createConnection aqui. Todas as validações de parâmetros de conexão ainda acontecerão (o que eu quero que elas aconteçam. Aspiro a trabalhar com o máximo de peças autênticas - portanto zombe do mínimo absoluto necessário para fazer um teste rápido). No entanto - o query é ridicularizado no protótipo e deve ser restaurado.

Observe também que, se você trabalhar cirurgicamente, o verify estará no método mocked, não no mockTarget.

Aqui está um bom recurso: http://devdocs.io/sinon~6-stubs/

2
Radagast the Brown