.toThrow
and .rejects.toThrow
should return the error thrown.
When working with a project that uses ava I noticed their .throws
and .throwsAsync
return the original error. It is very convenient.
This would make it possible to never need the expect.hasAssertions()
+ try / catch
syntax.
function throwSyncError() {
const syncError = new Error('sync error');
syncError.code = 'SYNC';
throw syncError;
}
async function throwAsyncError() {
const asyncError = new Error('sync error');
asyncError.code = 'ASYNC';
throw asyncError;
}
test('can get error message', async () => {
const syncError = expect(() => throwSyncError()).toThrow('sync error');
expect(syncError.code).toEqual('SYNC');
const asyncError = await expect(throwAsyncError()).rejects.toThrow('sync error');
expect(asyncError.code).toEqual('ASYNC');
});
Because using .toThrow
and .rejects.toThrow
over try/catch
it prevents tests that don't fail because they no longer reject. https://github.com/facebook/jest/issues/3917
Sounds like a good idea to me. @SimenB @thymikee @pedrottimark wdyt?
While I agree the idea is sound (I love this part of Ava's API), I'm not sure we should do this. All matchers today return void
(or Promise<void>
in the case of resolves
and rejects
) - changing that is a pretty big change. I'd definitely like @cpojer's and/or @scotthovestadt's thoughts on this before we do much work on it (sorry about not responding sooner, @chrisblossom!).
Note that this change will also break things like https://github.com/mattphillips/jest-chain (/cc @mattphillips who might have thoughts on this 馃檪)
As for my own opinion - I think this is better solved by a custom matcher that takes predicate rather than returning the error.
expect(() => { throw new Error('boom'); }).toThrowWithField(err => err.code === 'MODULE_NOT_FOUND');
or something like that. Such a matcher may or may not make sense to have in core, but that's a separate discussion
As for my own opinion - I think this is better solved by a custom matcher that takes predicate rather than returning the error.
Personally I disagree. But I also think that 90% of the matchers should be removed and should just be expressed as plain javascript with toEqual
. Less too learn, less to document.
I'd prefer to use the existing matcher if anything:
test('example', () => {
let errorThrown;
expect(() => {
throw new Error('boom');
}).toThrow((error) => {
errorThrown = error;
// boolean return (true passes, false fails)
return error.message === 'boom';
});
expect(errorThrown.code).toEqual('MODULE_NOT_FOUND');
});
But I think the PR as is feels a lot more natural / expected. Also I think it is best to provide the tools to never need try/catch
considering how easy it is to shoot yourself in the foot with it.
All matchers today return void (or Promise
in the case of resolves and rejects) - changing that is a pretty big change.
Just curious, why is this considered a big change?
Note that you can do this today:
test('example', () => {
expect(() => {
throw new Error('boom');
}).toThrow(expect.objectContaining({ message: 'boom' }));
});
For message
you can just add the string as .toThrow('boom')
, but the asymmetric expect.objectContaining
allows you to check any field (at any depth). And you can create your own custom asymmetric matchers if you want as well
Another big use case for this is dealing with aggregate errors. Examples: tc39/proposal-promise-any
and aggregate-error
. Seems very difficult to deal with with the current matching api. With this PR you could set the error and run your own expects
.
The type declarations say that matchers return the actual value. Is that intended to be the case, or is it a mistake?
https://github.com/facebook/jest/blob/master/packages/expect/src/types.ts#L118-L122
I'm hitting the same issue where it's annoying to assert against thrown values. I already know how to use expect(valueGoesHere).<matchers that I already understand how to use>()
It seems unnecessarily inconvenient that I have to use a different style of matching for thrown values.
expect(promise).rejects.<matcher here>()
can be used to extract the rejection of a promise and match against it. Can there be a sync variant? expect(fn).throws.toMatchObject({code: 'ENOENT'})
?
It's still not ideal because multiple assertions means multiple function invocations, which is not the case with the promise. The assertion library should be giving the developer a straightforward way to assert that something throws and get a reference to the thrown value.
@SimenB Is there any progress to this proposal? I really like it and would be greatly ease testing async functions.
Upvoting this feature as well. 馃崒 that it's been sitting for a year without action while other test suites support this.
Note that you can do this today:
test('example', () => { expect(() => { throw new Error('boom'); }).toThrow(expect.objectContaining({ message: 'boom' })); });
For
message
you can just add the string as.toThrow('boom')
, but the asymmetricexpect.objectContaining
allows you to check any field (at any depth). And you can create your own custom asymmetric matchers if you want as well
@SimenB Is this doced anywhere? I canot find.
Note that you can do this today:
test('example', () => { expect(() => { throw new Error('boom'); }).toThrow(expect.objectContaining({ message: 'boom' })); });
For
message
you can just add the string as.toThrow('boom')
, but the asymmetricexpect.objectContaining
allows you to check any field (at any depth). And you can create your own custom asymmetric matchers if you want as well
This is great, but I'd like to be able to use expect(thing()).rejects.toBeInstanceOf(SomeErrorClass) and additionally check that the thrown error has the correct data added to it (ie constructed with the right message or value).
This is great, but I'd like to be able to use expect(thing()).rejects.toBeInstanceOf(SomeErrorClass) and additionally check that the thrown error has the correct data added to it (ie constructed with the right message or value).
Currently I do
const promise = thing()
await expect(promise).rejects.toThrow(MyErrorClass)
await expect(promise).rejects.toThrow('Error message')
await expect(promise).rejects.toThrow(expect.objectContaining({ code: 'MY_CODE' }));
But it only kind of works for async code if you want thing()
to be evaluated only once. It's a bit clunky.
Most helpful comment
Note that you can do this today:
For
message
you can just add the string as.toThrow('boom')
, but the asymmetricexpect.objectContaining
allows you to check any field (at any depth). And you can create your own custom asymmetric matchers if you want as well