Модульное тестирование с помощью Bookshelf.js и knex.js

Я относительно новичок в Node и работаю над проектом, используя knex и книжную полку. У меня немного блок проблем, проверяющий мой код, и я не уверен, что я делаю неправильно.

В принципе у меня есть модель (называемая VorcuProduct), которая выглядит так:

var VorcuProduct = bs.Model.extend({
    tableName: 'vorcu_products'
});

module.exports.VorcuProduct = VorcuProduct

И функция, которая сохраняет файл VorcuProduct, если он не существует в БД. Довольно просто. Функция, выполняющая это, выглядит так:

function subscribeToUpdates(productInformation, callback) {
  model.VorcuProduct
    .where({product_id: productInformation.product_id, store_id: productInformation.store_id})
    .fetch()
    .then(function(existing_model) {
        if (existing_model == undefined) {
            new model.VorcuProduct(productInformation)
                .save()
                .then(function(new_model) { callback(null, new_model)})
                .catch(callback);
        } else {
            callback(null, existing_model)
        }
    })
}

Каков правильный способ проверить это без попадания в БД? Мне нужно высмеять fetch, чтобы вернуть модель или undefined (в зависимости от теста), а затем сделать то же самое с save? Должен ли я использовать rewire для этого?

Как вы можете видеть, я немного потерян, поэтому любая помощь будет оценена.

Спасибо!

Ответы

Ответ 1

Я использую базы данных Sqlite3 в памяти для автоматического тестирования с большим успехом. Мои тесты занимают от 10 до 15 минут, чтобы работать против MySQL, но всего за 30 секунд или около того в базе данных sqlite3 в памяти. Используйте :memory: для вашей строки подключения, чтобы использовать эту технику.

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

Gotchas - Knex/Bookshelf инициализирует соединение в начале приложения, а это означает, что вы сохраняете контекст между тестами. Я бы рекомендовал написать схему create/destroy script, чтобы вы могли создавать и уничтожать таблицы для каждого теста. Кроме того, Sqlite3 менее чувствителен к ограничениям внешнего ключа, чем MySQL или PostgreSQL, поэтому убедитесь, что вы запускаете приложение против одного из них каждый раз, а затем, чтобы убедиться, что ваши ограничения будут работать должным образом.

Ответ 2

Это действительно большой вопрос, который вызывает как ценность, так и ограничения модульного тестирования.

В этом конкретном случае незашифрованная логика довольно проста - просто простой блок if, поэтому можно утверждать, стоит ли это для тестирования единицы измерения, поэтому принятый ответ является хорошим и указывает на значение теста интеграции с малым масштабом.

С другой стороны, осуществление модульного тестирования по-прежнему ценно, поскольку оно указывает на возможности улучшения кода. В целом, если тесты слишком сложны, базовый код, вероятно, может использовать некоторый рефакторинг. В этом случае функция doesProductExist может быть реорганизована. Возвращение promises из knex/bookshelf вместо преобразования в обратные вызовы также будет полезным упрощением.

Но для сравнения здесь я беру на себя то, что будет выглядеть истинное модульное тестирование существующего кода:

var rewire = require('rewire');
var sinon = require('sinon');
var expect = require('chai').expect;
var Promise = require('bluebird');
var subscribeToUpdatesModule = rewire('./service/subscribe_to_updates_module');

var subscribeToUpdates = subscribeToUpdatesModule.__get__(subscribeToUpdates);

describe('subscribeToUpdates', function () {
  before(function () {
    var self = this;
    this.sandbox = sinon.sandbox.create();
    var VorcuProduct = subscribeToUpdatesModule.__get__('model').VorcuProduct;

    this.saveStub = this.sandbox.stub(VorcuProduct.prototype, 'save');
    this.saveStub.returns(this.saveResultPromise);

    this.fetchStub = this.sandbox.stub()
    this.fetchStub.returns(this.fetchResultPromise);

    this.sandbox.stub(VorcuProduct, 'where', function () {
      return { fetch: self.fetchStub };
    })

  });

  afterEach(function () {
    this.sandbox.restore();
  });

  it('calls save when fetch of existing_model succeeds', function (done) {
    var self = this;
    this.fetchResultPromise = Promise.resolve('valid result');
    this.saveResultPromise = Promise.resolve('save result');
    var callback = function (err, result) {
      expect(err).to.be.null;
      expect(self.saveStub).to.be.called;
      expect(result).to.equal('save result');
      done();
    };
    subscribeToUpdates({}, callback);
  });

  // ... more it(...) blocks

});