我有一个函数,它计算一些东西,通过回调来通知用户有关某些事件:
function returnAndCallback(callback) {
callback(5); // not always invoked
return 3;
}
使用Mocha和Should.js,我写了这个测试:
describe('mixing sync and async code', function() {
it('should test callback AND return value', function(done) {
returnAndCallback(function(value) {
value.should.be.eql(5);
done();
}).should.be.eql(4);
});
});
这是成功的,因为在调用done()
时测试结束。看来,我可以编写同步测试并检查返回值,或者我可以编写异步测试并检查回调。
不能像这样使用同步测试:
describe('mixing sync and async code', function() {
it('should test callback AND return value', function() {
returnAndCallback(function(value) {
should.be.eql('difficult to get result');
}).should.be.eql(3);
});
});
...因为我想声明回调被调用。如果从不调用回调函数,则此测试将成功。
如何测试使用正确值 AND 调用回调?返回正确的值?
复制测试是我看到的唯一选择。
编辑:我注意到,我在这里错误地使用了术语asynchron。回调只是将可选动作注入函数的一种方法,该函数转换对象。所有代码都是同步的,但控制流有时会分支到回调中,我希望能够识别出来。
以下是另一种测试方法:
describe('mixing sync and async code', function() {
it('should test callback AND return value', function() {
let callbackValue;
returnAndCallback(function(value) {
callbackValue = value;
}).should.be.eql(3);
callbackValue.should.be.eql(5);
});
});
但由于额外的变量,仍然不是很完美。
答案 0 :(得分:2)
首先,请注意done()
意味着同步测试; Mocha的默认设置是以异步方式运行测试 。如果要测试异步函数(在回调函数中返回值的函数)中的“返回”值,可以通过done()
同步运行它们。
接下来,您无法从异步函数返回值。这两种行为是互斥的:
function returnAndCallback(callback) {
callback(5); // not always invoked
return 3;
}
您希望仅执行回调。
在我看来,您希望有时传递回调,但并非总是如此。在这种情况下,我将功能测试分开(我认为你需要在任何地方使用done()
,以保持测试的同步性)并检查函数内部的回调。
现在我们已经澄清了,因为你想断言调用了一个回调,我们需要建立一些基线假设:
您想要测试这两件事。 A)很容易证明:你正在编写回调函数作为测试的一部分,所以如果你通过说null
或undefined
,当然测试会失败,但这不是重点这个测试。以下是你如何证明A)和B):
function optionalAsync(callback) {
if (typeof callback === 'function') {
callback(4)
} else {
return 3
}
}
describe('optional async function', function() {
it('should return 4 when a callback is passed', function(done) {
optionalAsync(function(value) {
should(value).be.eql(4)
done()
})
})
it('should return 3 when no callback is passed', function(done) {
optionalAsync().should.be.eql(3)
done()
})
})
这有点奇怪,但考虑到你的用例,检查这两种可能性是有意义的。我确信你可以减少代码占用空间,但我建议保持这种方式,以便在你搁置一年并忘记你所做的事情时可读性;)
现在,如果仍然想让某个函数同步运行,那么可以通过阻止事件循环https://stackoverflow.com/a/22334347/1214800来实现。
但你为什么要这样做?
避免在本质上非阻塞的异步平台中处理同步操作的麻烦,并使用回调编写所有内容(甚至是非IO阻塞操作):
function optionallyLongRunningOp(obj, callback) {
if (typeof callback === 'function') {
validateObject(obj, function(err, result) {
// not always long-running; may invoke the long-running branch of your control-flow
callback(err, result)
})
} else {
throw new Error("You didn't pass a callback function!")
}
}
describe('possibly long-running op async function', function() {
it('should return 4 when control flow A is entered', function(done) {
obj.useControlFlow = "A"
validateObject(obj, function(err, result) {
// this is a slow return
should(result.value).be.eql(4)
done()
})
it('should return 3 when control flow B is entered', function(done) {
obj.useControlFlow = "B"
validateObject(obj, function(err, result) {
// this is a quick return
should(result.value).be.eql(3)
done()
})
})
})
以下是您的回答,其中包含所有内容作为回调(即使是短操作):
var doLongRunnignOp = function(cb) {
var didNotify = true
cb(didNotify)
}
function doubleAndNotifyEven(num, cb) {
if (num % 2 == 0) {
doLongRunnignOp(function(didNotify) {
cb(num)
// did notify
})
} else {
cb(2 * num)
// instant return, did not notify
}
}
describe('checking return value and callback execution', function() {
it('should double and report given an even number', function() {
doubleAndNotifyEven(2, function(value) {
should(value).be.eql(2)
})
})
it('should double and not report anything given an odd number', function() {
doubleAndNotifyEven(3, function(value) {
should(value).be.eql(6)
})
})
})
答案 1 :(得分:1)
这是此问题的另一种解决方案。它只是在测试中增加了一行代码,以确保回调执行。
let should = require('should');
function doubleAndNotifyEven(num, reportEven) {
if (num % 2 == 0) {
reportEven(num);
}
return 2 * num;
}
function mandatoryCallback(callback) {
mandatoryCallback.numCalled = 0;
return function () {
mandatoryCallback.numCalled++;
return callback.apply(this, arguments);
};
}
describe('checking return value and callback execution', function() {
it('should double and report given an even number', function() {
doubleAndNotifyEven(2, mandatoryCallback(function(value) {
should(value).be.eql(2);
})).should.be.eql(4);
should(mandatoryCallback.numCalled).greaterThan(0);
});
it('should double and not report anything given an odd number', function() {
doubleAndNotifyEven(3, function(value) {
throw new Error('Wrong report!');
}).should.be.eql(6);
});
});
请注意sinon做类似的事情。