0

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

vidhill
  • 13
  • 1
  • 4

1 Answers1

3

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))
Estus Flask
  • 206,104
  • 70
  • 425
  • 565