1

I'm trying to write a jasmine test that has a spy which .and.returnValues a list of promises. The first several promises are rejections, the last is a success. While the test passes just fine, Node complains with the following:

UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): undefined
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): undefined
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 4): undefined
PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 2)
PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 3)
PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 4)

My code is very straight forward: I create a spy, inject it into my code, call my code which will loop calling my spy until it doesn't reject, and then monitor that it was called 5 times. The spy is like this:

var spy = jasmine.createSpy("spy").and.returnValues(
    Promise.reject(),
    Promise.reject(),
    Promise.reject(),
    Promise.reject(),
    Promise.resolve(true)
);

// Inject the spy...

// This will resolve only when the Promise-returning function it calls resolves without rejecting.
myFunc()   
    .then(() => {
        expect(spy).toHaveBeenCalledTimes(5);
        done();
    })
    .catch();

The code under test has last in it's chain an empty .catch() to validate that I'm not causing the problem there. AFICT, the problem is that Node sees me doing Promise.reject() and thinks that is unhandled, when, in fact, it is handled.

How can I properly test rejected promises? I feel like I need something like this for Jasmine.

cjbarth
  • 4,189
  • 6
  • 43
  • 62
  • why don't you use `async await`, wrap the `await` in a `try..catch..finally` block , in the `finally` block then you can do `expect(spy).toHaveBeenCaledTimes(5)`; – 0.sh Jan 05 '18 at 00:15
  • I'm on Node 6, where `async/await` isn't supported. – cjbarth Jan 05 '18 at 14:44

1 Answers1

2

This is because you catch the rejected promise somewhere later in the event queue and not in the same call stack where the promise is created.

A solution for this is:

var unsafeReject = p => {
  p.catch(ignore=>ignore);
  return p;
};

var p = Promise.reject("will cause warning");

//will catch reject on queue, not on stack
setTimeout(
  ()=>p.catch(e=>console.log("reject:",e))
);

var q = unsafeReject(Promise.reject("will NOT cause warning"));
setTimeout(
  ()=>q.catch(e=>console.log("reject:",e))
);
HMR
  • 37,593
  • 24
  • 91
  • 160
  • 1
    Did you mean `safeReject`? – Bergi Jan 11 '18 at 21:09
  • @Bergi Correct, actually the name is wrong, should be `unsafeReject` since the promise passed in will not "blow up" in the console when it's rejected but can still be caught on the returned promise. I'll rename it to `unsafeReject` – HMR Jan 12 '18 at 04:45
  • 1
    I guess I'd use either `handled(Promise.reject(…))` or `handledRejection(…)`. I would not use the "safe" terminology, as it's ambiguous whether it's safe to reject (without blowing anything up) or safe because you cannot miss rejections. – Bergi Jan 12 '18 at 04:53