I was having issues running a test (in Node),
I was simulating a promise being rejected, and my code should retry (using promise-retry if that could be relevant).
When I simulated the rejected promise using stub.returns(Promise.reject(error)
I was getting a uncaught error warnings (for my dummyErrors
), even though I am catching errors where I call my function...
-note, these uncaught errors were only happening in the unit tests not in real calls.
const mockedFunction = sinon.stub();
const dummyError = new Error('Document is locked');
mockedFunction.onCall(0).returns(Promise.reject(dummyError));
mockedFunction.onCall(0).returns(Promise.reject(dummyError));
mockedFunction.onCall(0).returns(Promise.reject(dummyError));
mockedFunction.onCall(1).returns(Promise.resolve({approved: true}));
I discovered that by changing to use the stub.rejects()
syntax:
mockedFunction.onCall(0).rejects(dummyError);
mockedFunction.onCall(1).rejects(dummyError);
mockedFunction.onCall(2).rejects(dummyError));
mockedFunction.onCall(3).resolves({approved: true});
I no longer get the uncaught error warnings.
My issue is solved, however I would like to get a better understand as to why, I looked at the sinon source code and it looks like the implementation of .rejects
is no different
In promise implementations that are intrinsically detect uncaught error (including V8/Node.js), an error from rejected promise should be caught on same tick, otherwise it triggers UnhandledPromiseRejectionWarning
.
This will work fine:
let promise = Promise.reject();
promise.catch(() => {});
This will result in potentially unhandled promise and trigger a warning:
let promise = Promise.reject();
setTimeout(() => {
promise.catch(() => {});
});
If Promise.reject(dummyError)
isn't chained with catch(...)
or then(..., ...)
on same tick, it triggers a warning, while in case of rejects(dummyError)
a rejected promise is created on function call, so this likely will be true:
sinon.spy(Promise, 'reject');
mockedFunction.onCall(0).rejects(dummyError);
expect(Promise.reject).to.have.not.been.called;
mockedFunction();
expect(Promise.reject).to.have.been.called;
An alternative to rejects(dummyError)
is:
mockedFunction.onCall(0).callsFake(() => Promise.reject(dummyError))