Reputation: 12553
I have a set of integration tests for message queueing code (over a real RabbitMQ). The tests are verifying that the correct use case is triggered when a specific message arrives.
it("should trigger the use case", () => {
const stub = sinon.stub(app, 'useCaseToTrigger').resolves();
await publishMessage({ id: "SOME_ID" })
// FAIL - Message isn't processed yet.
stub.should.have.been.calledWith(match({id: "SOME_ID" }))
})
This doesn't work. The await
statement waits for the message to be published, but says nothing of when the message is processed.
I can add a delay, but that quickly becomes a huge waste of time (if there are multiple tests).
it("should trigger the use case", () => {
const stub = sinon.stub(app, 'useCaseToTrigger').resolves();
await publishMessage({ id: "SOME_ID" })
await new Promise(r => setTimeout(r, 100));
// PASS - the message has been processed before the verification
stub.should.have.been.calledWith(match({id: "SOME_ID" }))
})
But if I make the stubbed function resolve a promise when called, I can get my test to work without any undue delay.
it("should trigger the use case", () => {
const promise = new Promise(r => {
sinon.stub(app, "useCaseToTrigger").callsFake(() => {
setImmediate(() => { r(); }) // Push to the end of the event loop
return Promise.resolve();
})
})
await publishMessage({ id: "SOME_ID" })
await promise;
// PASS - Message processed before continuing test
app.useCaseToTrigger.should.have.been.calledWith(match({id: "SOME_ID" }))
})
This does become a bit cumbersome to deal with (and probably not the easiest to read either). But it also gives eslint floating promises warnings. So to avoid those warnings, I need to rewrite it to:
it("should trigger the use case", () => {
await Promise.all([
new Promise(r => {
sinon.stub(app, "useCaseToTrigger").callsFake(() => {
setImmediate(() => { r(); }) // Push to the end of the event loop
return Promise.resolve();
})
}),
publishMessage({ id: "SOME_ID" })
]);
// PASS - Message processed before continuing test
app.useCaseToTrigger.should.have.been.calledWith(match({id: "SOME_ID" }))
})
Is there a way to make this cleaner.
Upvotes: 1
Views: 1455
Reputation: 74680
I think you can use an empty promise to await, so the app logic eventually resolves that promise when it is called. Sinon provides a promise fake that makes this a bit easier than creating a new Promise
with an external reference to the resolve
function.
const p = sinon.promise()
const stub = sinon.stub(app, 'useCaseToTrigger').callsFake(p.resolve)
await publishMessage({ id: "SOME_ID" })
await p
sinon.assert.calledWith(stub, { id: "SOME_ID" })
It looks like sinon .resolves()
uses Promise.resolve
at the time of the stub call, so it doesn't have a reference to the eventual promise at stub creation, otherwise a stub.promise
type of reference might have been useful here to avoid the additional promise setup.
Upvotes: 2