Related to #3601. I was reading .rejects and .toThrow docs and found inconsistent example imho. Google got me to #3601. Please could anyone help me to understand this issue if i misunderstood anything.
Worked up to version: 20.0.3 I think
Stopped working in version: after #4884 i think
Steps to reproduce the behavior:
sorry for broken code block. Couldn't fix
test("rejects-toThrow test", async () => {
const myError = new Error("error message")
// (1) [passes]
// rejects unwraps the reason to the string literal
await expect(Promise.reject('octopus')).rejects.toBe('octopus')
// (2) [passes]
// rejects unwraps the reason to myError object
await expect(Promise.reject(myError)).rejects.toBe(myError)
// (3) [passes]
// rejects unwraps the reason to the function which throws when called
// prettier-ignore
await expect(Promise.reject(() => {throw myError})).rejects.toThrow(myError)
// (4) [passes] why?
// INCONSISTENT BEHAVIOUR IMHO
// from test case (2) we know that rejects unwraps the reason to myError object
// toThrow supposed to work with functions and not objects that's why test cases (5),(5.1) fail as expected
// P.S. toThrow with error object as an argument checks error message equality
await expect(Promise.reject(myError)).rejects.toThrow(myError)
// (5) [fails]
// --------------------------------
// Matcher error: received value must be a function
// Received has type: object
// Received has value: [Error: error message]
// --------------------------------
expect(myError).toThrow(myError)
// (5.1) [fails]
// --------------------------------
// Matcher error: received value must be a function
// Received has type: string
// Received has value: "octopus"
// --------------------------------
expect('octopus').toThrow('octopus')
// (6) [fails]
// So what behaviour should be in this test case?
// A BUG IMHO
// from test case (1) we know that rejects unwraps the reason to the string literal
// from test case (5.1) we know that toThrow receiving a string fails with a Matcher error
// but here there is no Matcher error. Jest says:
// --------------------------------
// Expected substring: "octopus"
// Received function did not throw
// --------------------------------
// Why it now considers unwraped reason from rejects to be a function?
// P.P.S. toThrow with string as an argument checks error message includes the substring
await expect(Promise.reject('octopus')).rejects.toThrow('octopus')
})
As stated by @BetterCallSky in #3601 in his 1st message before mergin PR #4884
npx envinfo --preset jestPaste the results here:
System:
OS: Windows 10
CPU: (8) x64 AMD Ryzen 5 2500U with Radeon Vega Mobile Gfx
Binaries:
Node: 10.13.0 - C:\Program Files\nodejs\node.EXE
npm: 6.4.1 - C:\Program Files\nodejs\npm.CMD
"jest": "^24.5.0"
@pedrottimark do you wanna unwrap this one and comment on the inconsistencies?
.rejects.toThrow doesn't make sense and it's not good for the API to make it magically work with special-casing. There are easier ways to write clearer assertions. .rejects, as described, should return asyncExpect(unwrapRejectionValue(promise)) which can then be matched using any normal matcher.
.rejects.toThrow() implies that the rejection value is itself a function that will then be invoked by toThrow. It implies bizarre usage like expect(Promise.reject(function() {throw new Error()})).rejects.toThrow(Error) (this is shown in the example from @nshaikhinurov)
As is, it's difficult to figure out exactly when .rejects and/or .toThrow will behave and when they'll do something bizarre. This is not the kind of behavior anyone wants from an assertion library.
The ideal API should have:
expect(fn).toThrow() invokes function; asserts that it throws; returns expect(thrownValue); does not accept any arguments. (chained matchers take care of the rest)expect(asyncFn).toThrowAsync() invokes function; asserts that it returns rejected promise; returns expectAsync(unwrapRejectionValue(asyncFn()))expect(promise).rejects asserts that value is a promise that rejects; returns expectAsync(unwrapRejectionValue(promise)))The return value of .rejects and .resolves should also be a valid PromiseLike so they can be awaited directly without chaining a matcher. await expect(promise).rejects;
expect(fn).toThrow().toMatchObject({code: 'ENOENT'});
await expect(asyncFn).toThrowAsync().toMatchObject({code: 'ENOENT'});
await expect(asyncFn()).rejects.toMatchObject({code: 'ENOENT'});
For others who struggle with this issue for years, here is my workaround - https://github.com/DanielHreben/jest-matcher-specific-error
@SimenB @pedrottimark any updates on this?
I would love to be able to write such tests:
expect(fn).toThrow().toMatchObject({code: 'ENOENT'});
Most helpful comment
For others who struggle with this issue for years, here is my workaround - https://github.com/DanielHreben/jest-matcher-specific-error