为什么这些测试通过了?
Why are these tests passing?
我有这个功能:
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,它期望调用正好是一次,尽管无论我调用 callback() 多少次它总是通过。我做错了什么?
假设我们谈论的是 sinon-chai
它应该是 calledOnce
而不是 called.once
。如果你这样做 called
任意数量的调用 > 0 将通过测试。
没有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 允许您编写一段看起来像 属性 访问的代码(即断言本身不以函数调用结束)来断言...无论您想要断言什么。这使用 属性 getters 来执行必要的代码。
问题是,如果您输入错误或其他错误,表达式将简单地评估为 undefined
(您正在访问不存在的 属性)和 从未执行任何断言代码,从而导致测试通过(因为只有在抛出异常时测试才会失败)。
在你的例子中,有一个针对 called
的断言,这很可能 returns 是一个对象。不幸的是,该对象没有断言 once
,因此没有执行任何代码并且测试通过。
解决方案
您有 2 个选择:
- 升级到具有
Proxy
支持的 Chai 4 和 Node.js 版本(不确定在何处添加了代理支持,可能是 Node.js 5 或 6)- chai 引入了针对这些问题的保护措施通过 Proxy 对象代理所有 属性 访问,该对象检查您是否使用有效的断言
- 切勿使用 getters 进行断言,并始终以函数调用结束断言 - 这将确保如果您犯了错误,测试将因臭名昭著的
undefined is not a function
错误而失败
在我看来,第二种选择是首选,因为对测试用例的正确性毫无疑问。即使在受支持的平台上,仍然可以关闭 Chai 代理支持。
我有这个功能:
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,它期望调用正好是一次,尽管无论我调用 callback() 多少次它总是通过。我做错了什么?
假设我们谈论的是 sinon-chai
它应该是 calledOnce
而不是 called.once
。如果你这样做 called
任意数量的调用 > 0 将通过测试。
没有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 允许您编写一段看起来像 属性 访问的代码(即断言本身不以函数调用结束)来断言...无论您想要断言什么。这使用 属性 getters 来执行必要的代码。
问题是,如果您输入错误或其他错误,表达式将简单地评估为 undefined
(您正在访问不存在的 属性)和 从未执行任何断言代码,从而导致测试通过(因为只有在抛出异常时测试才会失败)。
在你的例子中,有一个针对 called
的断言,这很可能 returns 是一个对象。不幸的是,该对象没有断言 once
,因此没有执行任何代码并且测试通过。
解决方案
您有 2 个选择:
- 升级到具有
Proxy
支持的 Chai 4 和 Node.js 版本(不确定在何处添加了代理支持,可能是 Node.js 5 或 6)- chai 引入了针对这些问题的保护措施通过 Proxy 对象代理所有 属性 访问,该对象检查您是否使用有效的断言 - 切勿使用 getters 进行断言,并始终以函数调用结束断言 - 这将确保如果您犯了错误,测试将因臭名昭著的
undefined is not a function
错误而失败
在我看来,第二种选择是首选,因为对测试用例的正确性毫无疑问。即使在受支持的平台上,仍然可以关闭 Chai 代理支持。