【问题标题】:Unit testing with Bookshelf.js and knex.js使用 Bookshelf.js 和 knex.js 进行单元测试
【发布时间】:2015-03-06 14:20:41
【问题描述】:

我对 Node 比较陌生,并且正在使用 knex 和书架进行项目。我在单元测试我的代码时遇到了一些麻烦,我不确定我做错了什么。

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

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

module.exports.VorcuProduct = VorcuProduct

如果 VorcuProduct 在 DB 上不存在,该函数会保存它。非常简单。执行此操作的函数如下所示:

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 以返回模型或未定义(取决于测试),然后对save 执行相同操作?我应该为此使用重新布线吗?

如您所见,我有点迷茫,因此我们将不胜感激。

谢谢!

【问题讨论】:

    标签: node.js unit-testing bookshelf.js knex.js


    【解决方案1】:

    我一直在使用in-memory Sqlite3 databases 进行自动化测试,并取得了巨大成功。我的测试在 MySQL 上运行需要 10 到 15 分钟,但使用内存中的 sqlite3 数据库只需 30 秒左右。使用:memory: 作为您的连接字符串以利用此技术。

    关于单元测试的说明 - 这不是真正的单元测试,因为我们仍在对数据库运行查询。这是技术上的集成测试,但它在合理的时间段内运行,如果您有一个查询量很大的应用程序(比如我的),那么无论如何,这种技术在捕捉错误方面将比单元测试更有效。

    Gotchas - Knex/Bookshelf 在应用程序开始时初始化连接,这意味着您可以在测试之间保留上下文。我建议编写一个模式创建/销毁脚本,以便您为每个测试构建和销毁表。此外,与 MySQL 或 PostgreSQL 相比,Sqlite3 对外键约束的敏感度较低,因此请确保不时针对其中一个运行您的应用程序,以确保您的约束能够正常工作。

    【讨论】:

    • 感谢您分享您的经验。出于好奇,您正在运行多少测试?另外,设置是否涉及加载大量种子数据?
    • @thebearingedge 我运行了大约 70 个场景,总共大约 1,000 个黄瓜步骤。我在每个场景中设置和拆除 60 张桌子。使用内存中的 sqlite,只需不到半秒的时间。
    • 它有一些缺点。例如 sqlite3 不支持 jsonb 数据类型。
    【解决方案2】:

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

    在这种特殊情况下,非存根逻辑非常简单——只是一个简单的if 块,因此是否值得进行单元测试工作是有争议的,因此接受的答案是一个很好的答案,并指出小规模集成测试的价值。

    另一方面,进行单元测试的练习仍然很有价值,因为它指出了代码改进的机会。一般来说,如果测试太复杂,底层代码可能会使用一些重构。在这种情况下,doesProductExist 函数可能会被重构。从 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
    
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-03-25
      • 2011-02-20
      • 2015-09-16
      • 2015-09-01
      • 2014-12-25
      • 1970-01-01
      • 2012-08-31
      • 2017-09-19
      相关资源
      最近更新 更多