-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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
expect: Improve report when assertion fails, part 6 #7621
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is awesome, as usual 😀
packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap
Outdated
Show resolved
Hide resolved
Pictures of baseline at left and improved at right with assertion line and labelled values Expected value is substring of error message: Expected value is regexp to test error message: Expected value is class to test error instance: Expected value is object to test error message: Expected value is undefined: Matcher errors: |
Codecov Report
@@ Coverage Diff @@
## master #7621 +/- ##
==========================================
+ Coverage 68.27% 68.35% +0.08%
==========================================
Files 249 249
Lines 9623 9648 +25
Branches 5 6 +1
==========================================
+ Hits 6570 6595 +25
Misses 3051 3051
Partials 2 2
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great as always. I left a comment about Flow types
Instead, it threw: | ||
<red> Error</> | ||
<red> <dim>at jestExpect (</>packages/expect/src/__tests__/toThrowMatchers-test.js<dim>:24:74)</></>" | ||
Received error message: <red>\\"apple\\"</> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, love this separation, makes it way easier to trace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Proximity for the win :)
// Possible improvement also for toMatch inverse highlight matching substring. | ||
// $FlowFixMe: Cannot get error.message because property message is missing in undefined | ||
`Received error message: ${printReceived(error.message)}\n` + | ||
// $FlowFixMe: Cannot get error.stack because property stack is missing in undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is async code and theoretically there is a chance that error
is mutated somewhere in between the calls. Can we guard this the same as on the other branch with error !== undefined
?
This comment applies to all $FlowFixMe
s added in this file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As usual, your review comments are very helpful. Instead of the following code:
if (error && !error.message && !error.name) {
error = new Error(error);
}
which has incorrect edge cases for falsey exception values like throw 0
I will rewrite as robust destructuring to provide message
or name
as arguments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you agree if exception is primitive value, then assertion should fail for expected class?
Now the following assertion passes, because of if
statement in preceding comment:
expect(() => { throw true; }).toThrow(Error);
And for your enjoyment, here is the mother of all edge cases: () => { throw undefined; }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you agree if exception is primitive value, then assertion should fail for expected class?
Yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left one comment on toThrow(someObj)
, other than that LGTM!
|
||
const pass = equals(error, expectedError); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure making toThrow(someObj)
less strict is a good idea.
- It's sort of breaking in that people might rely on their tests checking that thrown errors have some extra properties that should be set. After updating Jest and changing some code, their tests may now incorrectly pass.
- Personally, it's also not the behavior I would expect. I would expect
toThrow(someObj)
to check more than just the message, because with these changes, it's almost the same astoThrow(someObj.message)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jeysal Your energy to comment on issues and pull requests is like a New Year gift to Jest!
I will use the concepts from your first bullet point to evaluate changes to this PR so toThrow
matcher is correct and report is clear when the received exception isn’t an error object.
When the expected value is an error object, can you believe that equals
has following code:
if (a instanceof Error && b instanceof Error) {
return a.message == b.message;
}
So this pull request:
- moves similar logic into
toThrow
and removes ambiguity from its report - prepares code for possible future breaking change, see Confusing output from expect.toThrow #4724 although I have reservations about the specific request to match concatenated name and message
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jeysal Your energy to comment on issues and pull requests is like a New Year gift to Jest!
Been considering getting involved in this project for a few months now, Jest is so awesome and miles ahead of most other test runners 😄 No better time to start than the holidays, let's see if I can keep this up alongside work.
When the expected value is an error object, can you believe that
equals
has following code:
Oh okay, I didn't consider that equals
might have such special handling.
So something like
const expected = new Error('stuff');
expected.extra = 1337;
expect(() => {
const actual = new Error('stuff');
actual.extra = 42;
throw actual;
}).toThrow(expected);
already passes, I see (although it still feels weird).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jeysal I agree with Mark, your contributions are super appreciated! I'd like to invite you to the team's chat on discord - if you're interested, could you send me an email so I can give you an invite link? 🙂 My email is in my profile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, sent you an email! 👍
3 code snippets if y’all want to comment while I let them simmer in mental slow cooker tonight: type ThrownProps = {
message: string, // either exception.message or String(exception)
name?: string, // undefined if exception is not error object
stack?: string, // undefined if exception is not error object
}; let thrownProps = null;
if (fromPromise && isError(received)) {
thrownProps = getThrownProps(received);
} else {
if (typeof received !== 'function') {
// …
} else {
try {
received();
} catch (exception) {
thrownProps = getThrownProps(exception);
}
}
} const formatReceived = (
label: string,
thrownProps: ThrownProps | null,
key: string
) => thrownProps !== null && typeof thrownProps[key] === 'string'
? `${label}${printReceived(thrownProps[key])}\n`
: '';
// This helper is so we can easily see that corresponding labels and values align.
const formatExpected = (label, expected) => `${label}${printExpected(expected)}\n`; |
Before I commit code changes, here are pictures for y’all to critique. The currently approved improved is at left and proposed improved is at right.
Expected value is undefined: Expected value is substring of error message or exception value: |
I'd prefer "thrown value" over "exception value" And maybe |
Yes, I will change label to Here are the corresponding pretty formatted values for string versus regexp argument: Expected pattern: "an error"
Expected pattern: /an error/ If I understand what you mean, when the pattern is a string value, it is not as obvious that it means the criterion is whether the received message includes it as a substring? |
|
How about Possible future improvement for Also for |
|
+1 for "expected substring" |
Changes indirectly motivated by feedback: reduce length of labels and support non-error values.
Pictures of baseline at left and improved at right supersede preceding comments. Expected value is substring of error message: Expected value is regexp to test error message: Expected value is class to test error instance: Expected value is object to test error message: Expected value is undefined: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a nice overall improvement. Ideas why code-frames on the screenshots differ?
Good eyes to notice inconsistent stack frames.
Complete picture with stack for Complete picture for non-error thrown value therefore only stack for test: |
Aha, last test in picture for expected class was wrong. When I rewrote as Here is updated picture: Depending on how classes that extend |
@pedrottimark this is awesome work! Feel free to merge if you consider it complete 🙂 |
* expect: Improve report when assertion fails, part 6 * Fix flow errors * Move new function to reduce noise in diff * Update CHANGELOG.md * Convert ANSI codes in promise/async snapshots * Rewrite promise/async snapshot tests * Reduce length of labels and handle non-error values * Add tests for non-error values * Fix prettier lint error * Update ExpectAPI.md * Improve grammar in ExpectAPI.md * Delete an exception from Received function did not throw * Correct typo in ExpectAPI.md * Correct type and report for non-error class
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Summary
Goal: people can quickly see information that is relevant to them when
toThrow
failsTo replace redundant descriptions, and be consistent with stack frame, format as regular black:
Replace arrow function with classic function to refer to properties of non-lexical
this
Because of indentation change in
createMatcher
try review with option to ignore white spaceReplace message separated from stack or serialized by
pretty-format
with labels and valuesFactor out logic for
throw
statement with value other than Error objectFor expected:
test
method because that is the condition ofelse if
equals
function witherror.message === expected.message
I didn’t DRY out the
message
functions as much as theoretically possible:.toMatch
inverse highlight matching substringTest plan
Updated tests:
.toThrowError
tests).not
modifier)See also pictures in following comment