jest: toThrow should return the error
š Feature Proposal
.toThrow
and .rejects.toThrow
should return the error thrown.
Motivation
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.
Example
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');
});
Pitch
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
About this issue
- Original URL
- State: open
- Created 5 years ago
- Reactions: 18
- Comments: 17 (7 by maintainers)
Commits related to this issue
- feature(expect): Return the error from `toThrow` and `toThrowError` Closes #8698 — committed to chrisblossom/jest by chrisblossom 5 years ago
- feature(expect): Return the error from `toThrow` and `toThrowError` Closes #8698 — committed to chrisblossom/jest by chrisblossom 5 years ago
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 wellWhile 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
(orPromise<void>
in the case ofresolves
andrejects
) - 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.
or something like that. Such a matcher may or may not make sense to have in core, but thatās a separate discussion
@SimenB Is this doced anywhere? I canot find.
Currently I do
But it only kind of works for async code if you want
thing()
to be evaluated only once. Itās a bit clunky.For the sake of keeping Jest consistent, Iām actually fine with this approach. However, I think it would be useful for
toThrow
to accept a callback.Itās basically just a callback that enables the user to perform their own assertions on the thrown error.
Ideally, users should be able to avoid making custom matchers. Thatās probably its own barrier to entry. Iāve been using Jest for years, and Iāve never had to make custom matchers. I imagine this is why A)
jest
has so many matchers out of the box to begin with and B) popular libraries often provide matchers that prevent their users from worrying about this. This is great. But for a typical user, it also makes the idea of creating matchers intimidating. I imagine such an approach is avoided for the average person.Resorting to āMake a custom matcherā kind of defeats the point and raises a number of problems. Moreover, as chrisblossom, said, leaving the
try/catch
in the userās hands increases the chance of making mistakes.The current
toThrow
doesnāt seem to expect a function. So it could easily be overloaded and given aif (typeof input === 'function')
portion, I would presume. (I havenāt read the code yet.) Thoughts? @SimenB @chrisblossom And/or @cpojer and @scotthovestadt? since it seems Simen really wanted your opinions too.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.
I noticed that
jest
is normalizing errors, getting rid of some fields. So for example, Iām writing a CLI that Iām testing viaexeca
and I want to inspect theexitCode
for the expected error. Butawait expect(promise).rejects.toThrow(expect.objectContaining({ exitCode: 'MY_CODE' }))
doesnāt work asjest
has omitted theexitCode
from theexeca
error!I was able to workaround this an make a
getError
function that reliably returns the expected error unmodified:Usage:
Implementation:
Just realized this is exactly what the lint rule recommends š so not my idea
Being able to analyze the error in the usual jest chained functional style feels like it should be part of the standard api, especially with the discouragement of try{} catch{}
Welp. For anyone whoās interested, I had the (bad) idea of trying out matchers for the first time. Iām not sure whether or not people will find it useful, but Iām leaving it behind as an artifact just in case⦠while people are waiting to hear back from the Jest devs.
My solution should solve both sync and async use cases. @Janpot could you let me know if you find this useful? You still have to write a bit of code with my solution, but hopefully the approach looks less hackish to you.
Jest Code (TypeScript)
Note that I tested my code locally, and it works in all of the anticipated use cases. So if you use my code and it doesnāt work, then either A) You have an outdated version of
jest
ortypescript
(or some other environment problem), or B) I copy-pastaād wrong⦠in which case you should ping me. šExtend
jest
wherever you want ā in your test file, or in a file that you import:For TS users, youāll still have to update the types for Jest. In a type declaration file of your choosing, apply the following:
(Note that I use TS ESLintās recommended settings. So I have a comment for disabling a linter warning.)
I would still love to hear from the Jest devs about updating their
toThrow
matcher.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).
Upvoting this feature as well. š that itās been sitting for a year without action while other test suites support this.
Another big use case for this is dealing with aggregate errors. Examples:
tc39/proposal-promise-any
andaggregate-error
. Seems very difficult to deal with with the current matching api. With this PR you could set the error and run your ownexpects
.Sounds like a good idea to me. @SimenB @thymikee @pedrottimark wdyt?