Test a rejection with Chai as promised
Asked Answered
J

7

53

I want to test a function returning a promise.

In this particular test, the promise is expected to be rejected with an Error object containing the classical message field (in this test, it is expected to equal "my error message") and a custom field I added named code, which is a string (like "EACCESS", "ERIGHT", etc, in this test it is expected to equal "EFOO")

I want to use chai-as-promised for that.

return expect(foo()).to.eventually.be.rejectedWith("my error message");

This assertion is working but now I would like to test the code field too.
How to do that?

Joacimah answered 31/3, 2015 at 7:56 Comment(1)
Duplicate for Testing for specific properties of rejected promisesDynah
L
70

If you're using Chai-As-Promised (as you say you are), then it allows for chaining off of rejectedWith - and it sets the chain assertion object to be the error object - meaning anything after rejectedWith() is now going to assert on the Error. This lets you do cool things like:

return expect(foo()).to.eventually
  .be.rejectedWith("my error message")
  .and.be.an.instanceOf(Error)
  .and.have.property('code', 'EFOO');

Some of the chai methods also chain, so you can use that to make some quite deeply nested assertions about the error:

return expect(foo()).to.eventually
  .be.rejectedWith("my error message")
  .and.have.property('stack')
    .that.includes('myfile.js:30')
Layette answered 25/9, 2015 at 20:39 Comment(10)
for some reason this doesn't work for me. no matter what i make the error message string it always passes.Degradation
@Degradation can you paste the method code of foo(). You can verify your system under test (method) if you are using return statement and in the rejection handler, if you explicitly reject... else promises will be considered as resolved.Optimize
foo() was returning a $q.defer().promise -- apparently chai-as-promised has issues with angular's $q.Degradation
Worr, that is some buff chaining!Paleoecology
Doesn't work here either, basically using exactly this code minus the property check and have applied the chai.use(...) too.Legman
Only solution I found was to do this, otherwise it incorrectly passed when resolved expect(myPromise()).to.eventually.be.rejectedWith("message").then(() => done()); the done(...) bit only seems to be called if it rejects.Legman
-1 for not punning it up with "If you're using chai-as-promised (as promised in the question)", but +2 for the clear, concise answer. One thing I'd like addressed, though, is whether or not rejected will do the same.Stopping
@Degradation you need to return the expect function, otherwise it won't work. I was banging my head for the past 30 minutes until I've noticed that I forgot to return, now it's working for me.Cognac
@ChristianSaiki thank you much! I was banging my head against the wall over the exact same problem, for the past 20 minutes.Preteritive
@ChristianSaiki it seems to me that the better solution is to await the result. This is required because the function is called in an async context and won't be done executing when the test suite itself is already finished.Mckelvey
N
41

Having version 5.1.0 of ChaiAsPromised, solution from Keithamus did not work for me - rejectedWith did not gave me the error object to assert, but "rejected" did:

return expect(foo())
    .to.be.rejected
    .and.be.an.instanceOf(Error)
    .and.have.property('code', 'EFOO');

For asserting multiple properties

return expect(foo())
    .to.be.rejected
    .then(function(error) {
        expect(error).to.have.property('name', 'my error message');
        expect(error).to.have.property('code', 'EFOO');
    });
Niemeyer answered 25/11, 2015 at 11:25 Comment(0)
C
15

@Markko Paas's solution didn't work for me until I added 'eventually', or else rejected value is always {} empty object.

return expect(foo())
    .to.eventually.be.rejected
    .and.be.an.instanceOf(Error)
    .and.have.property('code', 'EFOO');
Chamfron answered 11/3, 2018 at 14:13 Comment(0)
B
9

In my case, since I was using chai-as-promised in an async function, all I had to do is add an await statement before expect(promise).to.be.rejectedWith(errorMessage), e.g:

it('should reject', async () => {
    await expect(promise).to.be.rejectedWith(errorMessage);
//  ^^^^^
});
Blackstock answered 15/3, 2021 at 15:28 Comment(0)
D
8

You can perform complex tests on errors using rejected.then:

it('throws a complex error', function () {
  return expect(foo()).to.eventually.be.rejected.then((error) => {
    expect(error.code).to.equal('expected code');
    // other tests
    // alternatively,
    expect (error).to.eql({
      foo: 'foo',
      bar: 'bar
    });
  });
});
Datum answered 28/6, 2018 at 16:54 Comment(0)
T
1

Chai-As-Promised did not work for me, because it does not throw if you expect something to be rejected and it does not reject.

Then I used the following, which IMO is also quite expressive:

//...
await $radioButton.click();

const executed = await(async () => {
    try {
        await tools.waitUntil(() => {
            return consoleMessages.length === 2;
        }, 1000); // 1000 is the timeout in milliseconds. waitUntil() rejects if it does timeout.
        return true;
    } catch (error) {
        return false;
    }
})();

chai.assert.strictEqual(executed, false);

Towers answered 8/2, 2021 at 15:56 Comment(0)
T
0

Use then and catch to avoid these issues.

it('should handle error during company details retrieval', async () => {
    const companyId = mongoose.Types.ObjectId();
    const params = {
      startDate: Date.now() - 7 * 24 * 3600 * 1000, // 7 days ago
      endDate: Date.now(),
    };

    const companyStub = sinon
      .stub(Company, 'findById')
      .rejects(new Error('Database error'));

    companyDashBoardInfo(companyId, params).then((result) => {
      expect(result).to.be.rejectedWith(InternalServerException);
      expect(
        companyStub.calledOnceWithExactly(companyId, {
          _id: 1,
          accountAccess: 1,
        }),
      ).to.be.true;
    });
  });
Twentieth answered 31/8, 2023 at 13:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.