Skip to content

rejects.toThrow compares only against the message, not the error object #11693

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dankolesnikov opened this issue Jul 25, 2021 · 8 comments
Closed

Comments

@dankolesnikov
Copy link

🐛 Bug Report or Feature Request

Unable to match on the object of the error of the rejected promise.

To Reproduce

await expect(axios(badUrl)).rejects.toThrow()

Expected behavior

Being able to access the error object and match on its objects.

await expect(axios(badUrl)).rejects.toThrow( (error) => error.reponse.status === 404)

Essentially I want to do this but without ES Lint yelling at me:

 try {
      await axios(url);
    } catch (error) {
      expect(error.response.status).toBe(404);
    }
@sigveio
Copy link
Contributor

sigveio commented Jul 29, 2021

Hey @dankolesnikov!

This is not a bug with Jest.

There are multiple ways you could access the error object in an async test:

For example..

test('should catch 404', async () => {
  expect.assertions(1);
  await axios('https://google.com/test404').catch((error) => {
    expect(error.response.status).toBe(404);
  });
});

Or...

test('should catch 404 (2)', async () => {
  expect.assertions(1);
  try {
    await axios('https://google.com/test404');
  } catch (error) {
    expect(error.response.status).toBe(404);
  }
});

Note that the expect.assertions(1) is important when testing promises and/or making assertions inside a catch block. Jest has a nice documentation page on async testing where you can read more about this.

But you should also think about what is your responsibility to test. Generally, a good rule is not to write tests that verify the internal workings of external libraries. Because in this case, axios already have tests themselves verifying that it rejects the promise when a server returns a 404 or similar error response.

In the future, I would recommend asking for help on stackoverflow before creating an issue on GitHub. There are many more people who can help there. And it also makes the answer more easily available to others who might have the same or similar questions.

✌️

@ericprud
Copy link

I agree that one can work around this but the jest expect package appears to throw away the thrown error, keeping only the message. This leads to counter-intuitive behavior like the OP's or .toEqual(new MyHttpException(500, "b0rked")) accepting new MyHttpException(999, "b0rked")

@ericprud
Copy link

ericprud commented Dec 25, 2021

It looks like the prob is that anything that is instanceof Error gets oversimplified:

class Orphan {  // `instanceof Error` is false, toEqual compares by properties
    constructor(public status: number, public message: string) {}
}

class Child extends Error {  // `instanceof Error` is true, toEqual compares only by .message
    constructor(public status: number, message: string) {super(message);}
}

const o1a1 = new Orphan(1, "a");
const o1a2 = new Orphan(1, "a"); // o1a2 == o1a1
const o2a = new Orphan(2, "a");  // o2a.status != o1a1.status

const c1a1 = new Child(1, "a");
const c1a2 = new Child(1, "a");  // c1a2 == c1a1
const c2a = new Child(2, "a");   // c2a.status != c1a1.status

test("pass async Orphan equals Orphan", () => {expect(o1a1).toEqual(o1a2);})
test("fail async Orphan equals Orphan", () => {expect(o1a1).toEqual(o2a);})
test("pass async Child equals Child", () => {expect(c1a1).toEqual(c1a2);})
test("fail async Child equals Child", () => {expect(c1a1).toEqual(c2a);})

should pass, fail, pass, fail, but instead the fourth passes.

@CaioTeixeira95
Copy link

Hello, I was facing the same problem. I solved it using the .toMatchObject method, which compares the whole object received. For custom errors it fits well, what do you guys think?

Try the following example:

class MyCustomError extends Error {
  constructor(public message: string, public customProperty: string) {
    super(message)
  }
}

test('Custom errors', async () => {
  const err = Promise.reject(new MyCustomError('Some Error', 'some value for my property'))
  await expect(err).rejects.toMatchObject({ message: 'Some Error', customProperty: 'other value for my property' })
})

It returns:

expect(received).rejects.toMatchObject(expected)

- Expected  - 2
+ Received  + 1

  Object {
-   "customProperty": "other value for my property",
-   "message": "Some Error",
+   "customProperty": "some value for my property",
  }

@ericprud
Copy link

ericprud commented Jan 8, 2022

Nice! I played around with it a bit more and it turns out you need toMatchObject something which is NOT an Error:

const err = Promise.reject(new MyCustomError('Some Error', 'some value'))
await expect(err).rejects.toMatchObject(new MyCustomError('Some Error', 'other value')) // passes
await expect(err).rejects.toMatchObject({ message: 'Some Error', customProperty: 'other value' }) // fails

I recall this being because TS compiles Errors to something odd (at least before ES2015). I remember commenting on an issue or an SO to that effect but can't find it now. [Edit: here's one related TS issue]

I expanded my earlier tests (and fixed some labels) using your technique:

// Orphans and Children from two comments back
test(" pass       Orphan==Orphan",      () => {expect(o1a1).toMatchObject(o1a2);})
test(" fail       Orphan==Orphan",      () => {expect(o1a1).toMatchObject(o2a);})
test(" pass       Child ==Child ",      () => {expect(c1a1).toMatchObject(c1a2);})
test("!fail       Child ==Child ",      () => {expect(c1a1).toMatchObject(c2a);})
test(" fail       Child ==Child'",      () => {expect(c1a1).toMatchObject(Object.assign({}, c2a));})
test(" fail str(Child)==str(Child)",    () => {expect(JSON.stringify(c1a1)).toEqual(JSON.stringify(c2a));})
test(" pass async Child==Child ", async () => {await expect(Promise.reject(c1a1)).rejects.toMatchObject(c1a2);})
test("!fail async Child==Child ", async () => {await expect(Promise.reject(c1a1)).rejects.toMatchObject(c2a);})
test(" fail async Child==Child'", async () => {await expect(Promise.reject(c1a1)).rejects.toMatchObject(Object.assign({}, c2a));})
  ✓  pass       Orphan==Orphan (3 ms)
  ✕  fail       Orphan==Orphan (5 ms)
  ✓  pass       Child ==Child 
  ✓ !fail       Child ==Child  (1 ms)
  ✕  fail       Child ==Child' (1 ms)
  ✕  fail str(Child)==str(Child) (2 ms)
  ✓  pass async Child==Child  (1 ms)
  ✓ !fail async Child==Child 
  ✕  fail async Child==Child' (1 ms)

The '!'s flag the non-intuitive lack of failure caused but TSs special treatment of Errors and its subclasses.

@github-actions
Copy link

This issue is stale because it has been open for 1 year with no activity. Remove stale label or comment or this will be closed in 30 days.

@github-actions github-actions bot added the Stale label Feb 17, 2023
@github-actions
Copy link

This issue was closed because it has been stalled for 30 days with no activity. Please open a new issue if the issue is still relevant, linking to this one.

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Mar 19, 2023
@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 27, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants