João Otero
João Otero

Reputation: 998

Mocha / Node: how to use assert.rejects()?

I tried to followed the documentation in order to test for the error message using assert.rejects (I do have Node above v10).

But it's always passing, even with a ridiculous message. What am I doing wrong?

it("should fail but it's not", 
  async ()=> {
    let itemId = 'not an id'
    assert.rejects( 
      await ListRepo.checkById(itemId),
      {message: 'abracadabra'}                        
    )
  }
)

UPDATE: It seems that if I return the assert.rejects, it works. But I still don't know the reason.

Upvotes: 6

Views: 6048

Answers (2)

GrandOpener
GrandOpener

Reputation: 2123

As I write this, everything written above/previously is correct, but much of it is needlessly complicated. It is correct that you must return a promise, so that mocha itself has something to wait for, otherwise it returns immediately (before your promise can resolve or reject). The missing piece is that manually waiting for a promise is rarely what you actually want to do here.

This simplest pattern is hinted at by @caub in a comment--await the promise returned by assert.rejects. Specifically:

it("should fail but it's not", 
  async ()=> {
    let itemId = 'not an id'
    await assert.rejects( 
      ListRepo.checkById(itemId),
      {message: 'abracadabra'}                        
    )
  }
)

There are three key things here:

  1. The test function itself must be async.
  2. await the assert.rejects call inside the (async) test function. This automatically makes the test function return Promise<void>, which is exactly what you want. This puts the important part right at the point of the assert call, and compared to manually returning/handling a promise, makes it easier to copy/paste the call elsewhere and makes it easier to assert multiple rejections in one test.
  3. Do not await what you're passing into the rejects call. You should be passing in a promise--not an awaited value.

Upvotes: 12

dougpa
dougpa

Reputation: 41

What nicholaswmin said is correct, but there's a "rest of the owl" moment here that I'd like to clear-up.

If your desire is: "I want to assert reject", i.e. "I want to make sure this code rejects in the right way," the pattern is a bit complicated.

First off, write in return fxnWhichReturnsPromise(). The test will fail if your function is rejecting, and you want the inverse of that. You have to use the two-argument signature of .then(), which looks like: .then(onFulfillCallback, onRejectCallback), since if you split it into .then & .catch, failing in the .then-block will be caught by the subsequent .catch-block, and what should be a failing test (it did not reject) will be a passing one.

In practice, altogether it looks something like this:

it('rejects when given foobar', () => {
  return fxnThatShouldReject(foobar)
    .then(
      (val) => { assert.fail(`Should have rejected, returned with ${val} instead`)
      ,
      (err) => { assert.equal(err.message, "Rejection reason I was expecting") }
    )
}) 

The reason the assertion library does not have a built-in assertion handler for this is because being able to "hold-up" asynchronous execution in the test suite is really a test-runner capability. assert.throws() is synchronous, by contrast.

Upvotes: 1

Related Questions