使用Bookshelf.js和knex.js进行unit testing

我对Node相对比较陌生,正在使用knex和bookshelf开展一个项目。 我有一些unit testing我的代码有点麻烦,我不知道我做错了什么。

基本上我有一个模型(称为VorcuProduct),看起来像这样:

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

还有一个function可以将VorcuProduct保存在数据库中。 非常简单。 这样做的function如下所示:

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

哪种方法可以正确testing而不碰到数据库? 我需要模拟fetch返回一个模型或未定义(取决于testing),然后做同样的save ? 我应该使用rewire吗?

正如你可以看到我有点失落,所以任何帮助将不胜感激。

谢谢!

我一直在使用内存中的Sqlite3数据库进行自动化testing,取得了巨大的成功。 我的testing需要10到15分钟的时间来运行MySQL,但只有30秒左右的内存sqlite3数据库。 使用:memory:为您的连接string使用这种技术。

关于unit testing的注意事项 – 这不是真正的unit testing,因为我们仍在对数据库运行查询。 这是技术上的集成testing,但是它在合理的时间内运行,如果你有一个查询量大的应用程序(比如我的),那么这个技术将比unit testing更有效地捕捉错误。

陷阱 – Knex / Bookshelf在应用程序的开始处初始化连接,这意味着在testing之间保持上下文。 我会build议编写一个模式创build/销毁脚本,以便你和build立和销毁每个testing的表。 此外,Sqlite3对外键约束比MySQL或PostgreSQL更不敏感,所以确保你现在运行你的应用程序,以确保你的约束能够正常工作。

这实际上是一个很好的问题,它提出了unit testing的价值和局限性。

在这个特殊的情况下,非短截的逻辑非常简单 – 只是一个简单的if块,所以这是否值得unit testing是值得的,所以被接受的答案是一个很好的答案,并指出了小规模集成testing。

另一方面,unit testing的实践仍然是有价值的,因为它指出了代码改进的机会。 一般来说,如果testing过于复杂,底层代码可能会使用一些重构。 在这种情况下,一个doesProductExist函数可能被重构。 从knex / bookshelf返回的承诺,而不是转换为callback也将是一个有用的简化。

但是为了比较,我想了解一下对现有代码进行真正的unit testing:

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