Модульное тестирование с помощью 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
});