Как вы издеваетесь над MySQL (без ORM) в Node.js?

Я использую Node.js с Феликсом node-mysql клиент. Я не использую ORM.

Я тестирую с Vows и хочу иметь возможность издеваться над моей базой данных, возможно, используя Sinon. Так как у меня действительно нет DAL как такового (кроме node-mysql Я не совсем уверен, как это сделать. Мои модели в основном простые CRUD с большим количеством геттеров.

Есть идеи, как этого добиться?

6 ответов

Решение

С помощью sinon вы можете поместить макет или заглушку вокруг всего модуля. Например, предположим, mysql модуль имеет функцию query:

var mock;

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

queryString, queryParams вход, который вы ожидаете. rows это результат, который вы ожидаете.

Когда тестируемый класс теперь требует mysql и вызывает query метод, он будет перехвачен и проверен Sinon.

В вашем разделе ожиданий теста вы должны иметь:

mock.verify()

и в вашем демонтаже вы должны восстановить MySQL до нормальной функциональности:

mock.restore()

Это может быть хорошей идеей абстрагировать вашу базу данных в ее собственный класс, который использует mysql. Затем вы можете передать экземпляр этого класса конструкторам вашей модели вместо того, чтобы загружать его с помощью require().

С помощью этой настройки вы можете передать экземпляр mock db вашим моделям в файлах модульного теста.

Вот небольшой пример:

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

Я не совсем знаком с node.js, но в традиционном смысле программирования для такого тестирования необходимо абстрагироваться от метода доступа к данным. Не могли бы вы создать класс DAL, например:

var DataContainer = function () {
}

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

Теперь в контексте теста исправьте ваш класс getAllBooks во время инициализации, например:

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

Когда вызывается тестовый код, getAllBooks будет заменена версией, которая возвращает фиктивные данные вместо фактического вызова mysql. Опять же, это грубый обзор, так как я не совсем знаком с node.js

В итоге я начал с ответа @kgilpin и закончил с чем-то вроде этого, чтобы проверить Mysql в 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');
       });
   });
 });
});

Я не хотел никаких реальных подключений к базе данных, поэтому я вручную высмеивал все ответы mysql.
Добавил еще одну функцию .returns Вы можете издеваться над любым методом createConnection,

Вы можете смоделировать внешние зависимости, используя horaa

И я также верю, что модуль sandboxed для узла felixge может сделать нечто подобное.

Таким образом, используя тот же контекст kgilpin, в horaa это будет выглядеть примерно так:

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

Поскольку использование драйвера mysql требует, чтобы вы сначала создали соединение, и использовали apis возвращаемого контроллера соединений - вам нужен двухэтапный подход.

Есть два способа сделать это.

оцепить createConnection и вернуть ему заглушенное соединение

Во время настройки:

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

Во время срыва:

mysql.createConnection.restore();

Обратите внимание, что здесь query метод осмеивается в экземпляре, и не имеет никакого влияния на основной механизм, поэтому только createConnection должен быть восстановлен.

заглушение метода.query на прототип соединения

Эта техника немного сложнее, потому что mysql Драйвер официально не выставляет соединение для импорта. (ну, вы можете просто импортировать только модуль, реализующий соединение, но нет никакой гарантии, что какой-либо рефакторинг не переместит его оттуда). Таким образом, чтобы получить ссылку на прототип, я обычно создаю соединение и пересекаю цепочку конструктор-прототип:

Я обычно делаю это в одну строку, но я разбью его на шаги и объясню здесь:

Во время настройки:

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

Во время сноса

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

Обратите внимание, что мы не высмеиваем createConnection метод здесь. Все проверки параметров соединения будут по-прежнему происходить (что я и хочу, чтобы они происходили. Я стремлюсь работать с максимально аутентичными деталями - поэтому имитирую абсолютный минимум, необходимый для быстрого теста). Тем не менее query издевается над прототипом, и должен быть восстановлен.

Также обратите внимание, что если вы работаете хирургически, verify будет на поддельном методе, а не на mockTarget.

Вот хороший ресурс об этом: http://devdocs.io/sinon~6-stubs/

Другие вопросы по тегам