Javascript 使用Mocha混合同步和异步测试
我有一个函数,它计算一些东西,通过回调通知用户一些事件:Javascript 使用Mocha混合同步和异步测试,javascript,node.js,unit-testing,mocha.js,Javascript,Node.js,Unit Testing,Mocha.js,我有一个函数,它计算一些东西,通过回调通知用户一些事件: 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', fun
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);
});
});
。。。因为我想断言调用了回调。如果从未调用回调,则此测试将成功
如何测试调用回调时是否使用了正确的值,以及是否返回了正确的值
重复测试是我看到的唯一选择
编辑:我注意到,我在这里错误地使用了术语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);
});
});
但是由于额外的变量,仍然不完美。首先,请注意
done()
意味着同步测试;Mocha的默认设置是异步运行测试。如果要测试异步函数(在回调函数中返回值的函数)的“返回”值,可以通过done()
同步运行它们
接下来,不能从异步函数返回值。这两种行为是相互排斥的:
function returnAndCallback(callback) {
callback(5); // not always invoked
return 3;
}
您只希望执行回调
在我看来,您希望有时会传递回调,但并非总是这样。在这种情况下,我将分离函数测试(我认为您需要在任何地方使用done()
,以保持测试的同步性),并检查函数本身内部的回调
现在我们已经澄清了这一点,因为您希望断言调用了回调,所以我们需要建立一些基线假设:
- A)回调应该是函数
- B)应使用包含值的参数调用回调
null
或undefined
,测试当然会失败,但这不是本测试的重点。以下是您如何证明A)和B):
这有点奇怪,但是考虑到您的用例,检查这两种可能性是有意义的。我相信您也可以减少一点代码占用,但我建议您保持这种方式,以便在您将tis搁置一年而忘记您所做的事情时可读性;)
现在,在完成所有这些之后,如果您仍然希望有同步运行函数的选项,那么可以通过阻塞事件循环来实现
但你为什么要这么做
省去了在固有的非阻塞、异步平台中处理同步操作的麻烦,并使用回调写入所有内容(甚至是非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()
})
})
})
以下是您的答案,所有内容都作为回调(即使是简短的操作):
这是这个问题的另一个解决方案。它只是在希望确保回调执行的测试中添加了一行额外的代码
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);
});
});
还请注意哪些功能与此类似。我并不是想让人觉得迂腐,但您的功能似乎是错误的。如果你有一个回调,那就是你返回的。为什么有时会有回调,有时会有返回?具体的用例是一个验证器/检查器,它获取一个对象,检查它,可能会更改一些字段。可以进行回调,该回调报告所有更改。最后返回checked对象。在实践中,如何使用这样的函数?我使用Socket.IO并从大量不同的客户端实现中获取事件消息对象。我希望在处理这些消息时尽可能地健壮。有些人只是缺少一个我可以用默认值填充的字段,但我仍然想通知客户机(用于调试),因此我发送了一个警告事件,该事件将在回调中发出。如果真的出了什么问题,客户端会收到一个错误事件(当我想知道为什么gps位置出错时,这会帮助我更快地发现输入错误。我给出了一个latutide,但Chrome没有提示)。同样,我可以返回一个对象
{result:…,warning:[…],errors:[…]}
,但是,如果我能做msg.gps=validate(msg.gps,reportError,reportWarn)
,其中report*
函数是可选的或可选的no-ops,那么它看起来就不那么优雅了。非常感谢您的努力!这不完全是我想要的。我想我把问题说错了(同步和异步都搞砸了)。您对异步返回的处理是正确的,我现在接受这个答案。我编辑了这个问题,可以将其重新表述为“如何使该变量消失?”…如果您的值类似于null
或未定义整个测试中断,请不要执行value.should.be.eql(4)
。另外,为了获得完整的错误反馈,尝试{expect(value).to.equal(4);done();}catch(e){done(e);}
好的,是的,但是由于函数显式地设置了值(它不可能为null或未定义),我没有检查稳定性;)讨论的重点是,我质疑你为什么要同步写作?它破坏了整个节点模型。您可以异步编写所有内容,希望始终存在回调,然后简单地测试回调的值。将验证器构建到模块中并将其导出-您可以利用函数式编程设计将深层对象属性检查映射到模块中,然后返回
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)
})
})
})
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);
});
});