Как вы издеваетесь над MySQL (без ORM) в Node.js? - PullRequest
39 голосов
/ 05 декабря 2011

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

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

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

Ответы [ 6 ]

35 голосов
/ 12 апреля 2012

С помощью 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()
9 голосов
/ 15 марта 2012

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

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

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

// 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});
5 голосов
/ 31 мая 2017

Я закончил с ответа @ 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.

5 голосов
/ 07 января 2012

Я не совсем знаком с 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

3 голосов
/ 06 июня 2012

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

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

Таким образом, используя тот же контекст 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');
2 голосов
/ 13 сентября 2018

Поскольку использование драйвера 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/

...