chai: Deep equality check for Errors

Hi, because we use chai and chai-as-promised in our project, we recently tried to use .rejectedWith() in order to check for a custom error:

class HttpError extends Error {
    constructor(statusCode, message) {
        super(message);
        this.statusCode = statusCode;
        Object.setPrototypeOf(this, new.target.prototype);
        this.name = new.target.name;
    }
    toString() {
        return `${this.name}(${this.statusCode}): ${this.message}`;
    }
}

But the following check doesn’t work:

it('', async () => {
   const httpError = new HttpError(404, 'Not found.');
   await expect(Promise.reject(new HttpError(404, 'Not found.'))).to.be.rejectedWith(httpError);
});

This is - as @meeber kindly pointed out here - because of the strict === comparison of the throws() assertion in Chai itself, as chai-as-promised just emulates Chai’s throws().

I personally think that it’d be more useful for errors to have a deep equality check in place. Something like suggested in the linked issue: .to.deep.throw() => to.be.deep.rejectedWith() This deep equality check can’t be the same as the normal deep equality check for objects, because of the Error’s stack-trace property, which of course will be not the same in the most cases. I guess this approach would also not break the existing usage, so it might be a bit simpler than changing the error comparison as a whole.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 16 (8 by maintainers)

Commits related to this issue

Most upvoted comments

I agree with @janis91; I think our deep equality algorithm should special-case Error objects by ignoring the .stack property, but consider all other own and inherited enumerable properties.

Although the semantics are a bit weird, I also think Chai should support .deep.throw(errInstance) in order to perform a deep equality comparison against the thrown error, instead of strict equality. Such a change could ride in the wake of #1021.

@MicahZoltu part of the plan for Chai 5 is to have matchers, so you could do something like:

expect(error1).to.deep.equal({
  a: 'hello',
  b: expect.to.be.an.error(Error, /goodbye/)
})

The exact syntax and mechanics are not yet hashed out, but it would likely be close to something like this.

Actually the assert implementation by node.js actually works —

import * as assert from 'assert'

assert.deepStrictEqual(
  new Error('A'), 
  new Error('A')
) // passes

Understood and thank you for sharing this with me. I don’t have a lot of time, but I’m also not saying no, just working on a startup which is taking about 200% of my time 😉 One incentive to help attract maintainers is that github copilot comes free for maintainers of large projects: https://copilot.github.com I’ve been using it 20 days…and I’m def going to continue with it.

@keithamus Will that be able to be used as part of a .deep.equal(...)? My real problem looks like:

const error1 = [{a: 'hello', b: new Error('goodbye')}, ...]
const error2 = [{a: 'hello', b: new Error('goodbye')}, ...]
expect(error1).to.deep.equal(error2)