【问题标题】:Why are these tests passing?为什么这些测试会通过?
【发布时间】:2018-01-22 10:42:36
【问题描述】:

我有这个功能:

let removePresentation = function(presentationName, callback) {
  let rimraf = require('rimraf');

  callback();
  callback();
  callback();

  if(!presentationName || !presentationName.trim()) {
    callback();
    return;
  }

  presentationName = presentationName.replace('.zip', '');

  rimraf('./presentations/' + presentationName, function(err) {
    if(err) {
      console.log(err);
    }
    callback();
  });
};

exports.removePresentation = removePresentation;

我正在尝试使用以下方法对其进行测试:

var chai = require('chai'),
expect = require('chai').expect,
sinonChai = require('sinon-chai'),
sinon = require('sinon'),
mock = require('mock-require');

chai.use(sinonChai);

describe('removePresentation', function() {

  var sandbox;
  var callback;
  var rimrafSpy;

  beforeEach(function() {
    sandbox = sinon.sandbox.create();
    mock('../business/communications_business', {});

    rimrafSpy = sinon.spy();
    callback = sinon.spy();

    mock('rimraf', rimrafSpy);
  });

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

  it('should call rimraf if presentation name is valid', function(done) {
    let RoomStateBusiness = require('../business/roomstate_business');

    RoomStateBusiness.removePresentation('name.zip', callback);

    expect(rimrafSpy).to.have.been.calledWith('./presentations/name');
    expect(callback).to.have.been.called.once;
    done();
  });

  it('should not call rimraf if presentation name is null', function(done) {
    let RoomStateBusiness = require('../business/roomstate_business');

    RoomStateBusiness.removePresentation(null, callback);

    expect(rimrafSpy).not.to.have.been.called;
    expect(callback).to.have.been.called.once;
    done();
  });

  it('should not call rimraf if presentation name is whitespace', function(done) {
    let RoomStateBusiness = require('../business/roomstate_business');

    RoomStateBusiness.removePresentation('      ', callback);

    expect(rimrafSpy).not.to.have.been.called;
    expect(callback).to.have.been.called.once;
    done();
  });

  it('should not call rimraf if presentation name is empty string', function(done) {
    let RoomStateBusiness = require('../business/roomstate_business');

    RoomStateBusiness.removePresentation('', callback);

    expect(rimrafSpy).not.to.have.been.called;
    expect(callback).to.have.been.called.once;
    done();
  });

});

即使我清楚地多次调用 callback()(仅在测试时),expect(callback).to.have.been.called.once; 始终断言为真。我已经检查了 Chai api,它期望调用恰好是一次,尽管无论我调用多少次回调()它总是通过。我做错了什么?

【问题讨论】:

  • 您确定expect(callback).to.have.been.called.once; 有值吗?它只是一个吸气剂,除非你使用的是最新的稳定版chai,否则你永远不会知道。我建议将断言重写如下:expect(callback).to.have.callCount(1);

标签: javascript node.js sinon chai


【解决方案1】:

没有expect(fn).to.have.been.called.once这样的断言。

根据sinon-chai docs,只有:

  • expect(fn).to.have.been.called
  • expect(fn).to.have.been.calledOnce

问题

这是 chai 的一个已知问题,以及为什么 getter-only-assertions 是一件坏事。 Chai 允许您编写一段看起来像属性访问的代码(即断言本身不以函数调用结束)来断言...无论您想要断言什么。这使用属性获取器来执行必要的代码。

问题在于,如果您犯了错字或其他错误,表达式将简单地计算为undefined(您正在访问一个不存在的属性)并且不会执行任何断言代码 ,导致测试通过(因为只有在抛出异常时测试才会失败)。

在您的情况下,called 有一个断言,这很可能返回一个对象。不幸的是,该对象没有断言once,因此没有执行任何代码并且测试通过了。

解决方案

有 2 个选项可供您使用:

  • 升级到支持 Proxy 的 Chai 4 和 Node.js 版本(不确定在哪里添加了代理支持,可能是 Node.js 5 或 6)- chai 通过代理所有属性访问来引入针对这些问题的保护 Proxy检查您是否使用有效断言的对象
  • 切勿使用 getter 进行断言,并始终以函数调用结束断言 - 这将确保如果您犯了错误,测试将因臭名昭著的 undefined is not a function 错误而失败

在我看来,第二个选项是非常受欢迎的,因为毫无疑问测试用例的正确性。即使在受支持的平台上,仍然可以关闭 Chai 代理支持。

【讨论】:

    【解决方案2】:

    假设我们谈论的是sinon-chai,它应该是calledOnce 而不是called.once。如果您执行called,任何数量> 0 的调用都将通过测试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-05
      相关资源
      最近更新 更多