Skip to content

Commit

Permalink
add stack trace on expect.(has)Assertions
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Apr 15, 2018
1 parent 65d3d8d commit 9b1e400
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 46 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -63,6 +63,8 @@

### Fixes

* `[expect]` Add stack trace when `expect.assertions` and `expect.hasAssertions`
causes test failures. ([#5997](https://github.com/facebook/jest/pull/5997))
* `[jest-runtime]` Throw a more useful error when trying to require modules
after the test environment is torn down
([#5888](https://github.com/facebook/jest/pull/5888))
Expand Down
86 changes: 85 additions & 1 deletion integration-tests/__tests__/__snapshots__/failures.test.js.snap
Expand Up @@ -36,37 +36,87 @@ exports[`not throwing Error objects 4`] = `
✕ throws on assertion
.hasAssertions()
✕ throws when there are not assertions
● .assertions() › throws
expect(received).toBeTruthy()
Received: false
11 | const throws = () => {
12 | expect.assertions(2);
> 13 | expect(false).toBeTruthy();
| ^
14 | };
15 | const redeclare = () => {
16 | expect.assertions(1);
at __tests__/assertion_count.test.js:13:17
● .assertions() › throws
expect.assertions(2)
Expected two assertions to be called but received one assertion call.
10 |
11 | const throws = () => {
> 12 | expect.assertions(2);
| ^
13 | expect(false).toBeTruthy();
14 | };
15 | const redeclare = () => {
at __tests__/assertion_count.test.js:12:10
● .assertions() › throws on redeclare of assertion count
expect(received).toBeTruthy()
Received: false
15 | const redeclare = () => {
16 | expect.assertions(1);
> 17 | expect(false).toBeTruthy();
| ^
18 | expect.assertions(2);
19 | };
20 |
20 |
at __tests__/assertion_count.test.js:17:17
● .assertions() › throws on assertion
expect.assertions(0)
Expected zero assertions to be called but received one assertion call.
20 |
21 | const noAssertions = () => {
> 22 | expect.assertions(0);
| ^
23 | expect(true).toBeTruthy();
24 | };
25 |
at __tests__/assertion_count.test.js:22:10
● .hasAssertions() › throws when there are not assertions
expect.hasAssertions()
Expected at least one assertion to be called but received none.
25 |
26 | const hasNoAssertions = () => {
> 27 | expect.hasAssertions();
| ^
28 | };
29 |
30 | describe('.assertions()', () => {
at __tests__/assertion_count.test.js:27:10
"
`;
Expand Down Expand Up @@ -192,6 +242,8 @@ exports[`works with assertions in separate files 1`] = `
exports[`works with async failures 1`] = `
"FAIL __tests__/async_failures.test.js
✕ something async
✕ hasAssertions
✕ assertions
● something async
Expand All @@ -214,6 +266,38 @@ exports[`works with async failures 1`] = `
at packages/expect/build/index.js:160:61
● hasAssertions
expect.hasAssertions()
Expected at least one assertion to be called but received none.
14 |
15 | test('hasAssertions', () => {
> 16 | expect.hasAssertions();
| ^
17 |
18 | return Promise.resolve('foobar');
19 | });
at __tests__/async_failures.test.js:16:10
● assertions
expect.assertions(42)
Expected 42 assertions to be called but received zero assertion calls.
20 |
21 | test('assertions', () => {
> 22 | expect.assertions(42);
| ^
23 |
24 | return Promise.resolve('foobar');
25 | });
at __tests__/async_failures.test.js:22:10
"
`;
Expand Down
26 changes: 1 addition & 25 deletions integration-tests/__tests__/failures.test.js
Expand Up @@ -18,30 +18,6 @@ const normalizeDots = text => text.replace(/\.{1,}$/gm, '.');

SkipOnWindows.suite();

const cleanupStackTrace = stderr => {
const STACK_REGEXP = /^.*at.*(setup-jest-globals|extractExpectedAssertionsErrors).*\n/gm;
if (!STACK_REGEXP.test(stderr)) {
throw new Error(
`
This function is used to remove inconsistent stack traces between
jest-jasmine2 and jest-circus. If you see this error, that means the
stack traces are no longer inconsistent and this function can be
safely removed.
output:
${stderr}
`,
);
}

return (
stderr
.replace(STACK_REGEXP, '')
// Also remove trailing whitespace.
.replace(/\s+$/gm, '')
);
};

test('not throwing Error objects', () => {
let stderr;
stderr = runJest(dir, ['throw_number.test.js']).stderr;
Expand All @@ -51,7 +27,7 @@ test('not throwing Error objects', () => {
stderr = runJest(dir, ['throw_object.test.js']).stderr;
expect(extractSummary(stderr).rest).toMatchSnapshot();
stderr = runJest(dir, ['assertion_count.test.js']).stderr;
expect(extractSummary(cleanupStackTrace(stderr)).rest).toMatchSnapshot();
expect(extractSummary(stderr).rest).toMatchSnapshot();
stderr = runJest(dir, ['during_tests.test.js']).stderr;
expect(extractSummary(stderr).rest).toMatchSnapshot();
});
Expand Down
12 changes: 12 additions & 0 deletions integration-tests/failures/__tests__/async_failures.test.js
Expand Up @@ -11,3 +11,15 @@
test('something async', () => {
return expect(Promise.resolve({foo: 'bar'})).resolves.toEqual({baz: 'bar'});
});

test('hasAssertions', () => {
expect.hasAssertions();

return Promise.resolve('foobar');
});

test('assertions', () => {
expect.assertions(42);

return Promise.resolve('foobar');
});
30 changes: 17 additions & 13 deletions packages/expect/src/extract_expected_assertions_errors.js
Expand Up @@ -25,13 +25,15 @@ const resetAssertionsLocalState = () => {
};

// Create and format all errors related to the mismatched number of `expect`
// calls and reset the matchers state.
// calls and reset the matcher's state.
const extractExpectedAssertionsErrors = () => {
const result = [];
const {
assertionCalls,
expectedAssertionsNumber,
expectedAssertionsNumberError,
isExpectingAssertions,
isExpectingAssertionsError,
} = getState();

resetAssertionsLocalState();
Expand All @@ -43,34 +45,36 @@ const extractExpectedAssertionsErrors = () => {
const numOfAssertionsExpected = EXPECTED_COLOR(
pluralize('assertion', expectedAssertionsNumber),
);
const error = new Error(

expectedAssertionsNumberError.message =
matcherHint('.assertions', '', String(expectedAssertionsNumber), {
isDirectExpectCall: true,
}) +
'\n\n' +
`Expected ${numOfAssertionsExpected} to be called but received ` +
RECEIVED_COLOR(pluralize('assertion call', assertionCalls || 0)) +
'.',
);
'\n\n' +
`Expected ${numOfAssertionsExpected} to be called but received ` +
RECEIVED_COLOR(pluralize('assertion call', assertionCalls || 0)) +
'.';

result.push({
actual: assertionCalls,
error,
error: expectedAssertionsNumberError,
expected: expectedAssertionsNumber,
});
}
if (isExpectingAssertions && assertionCalls === 0) {
const expected = EXPECTED_COLOR('at least one assertion');
const received = RECEIVED_COLOR('received none');
const error = new Error(

isExpectingAssertionsError.message =
matcherHint('.hasAssertions', '', '', {
isDirectExpectCall: true,
}) +
'\n\n' +
`Expected ${expected} to be called but ${received}.`,
);
'\n\n' +
`Expected ${expected} to be called but ${received}.`;

result.push({
actual: 'none',
error,
error: isExpectingAssertionsError,
expected: 'at least one',
});
}
Expand Down
25 changes: 18 additions & 7 deletions packages/expect/src/index.js
Expand Up @@ -314,19 +314,30 @@ const _validateResult = result => {
}
};

function assertions(expected: number) {
const error = new Error();
Error.captureStackTrace(error, assertions);

getState().expectedAssertionsNumber = expected;
getState().expectedAssertionsNumberError = error;
}
function hasAssertions(expected: number) {
const error = new Error();
Error.captureStackTrace(error, hasAssertions);

utils.ensureNoExpected(expected, '.hasAssertions');
getState().isExpectingAssertions = true;
getState().isExpectingAssertionsError = error;
}

// add default jest matchers
setMatchers(matchers, true, expect);
setMatchers(spyMatchers, true, expect);
setMatchers(toThrowMatchers, true, expect);

expect.addSnapshotSerializer = () => void 0;
expect.assertions = (expected: number) => {
getState().expectedAssertionsNumber = expected;
};
expect.hasAssertions = (expected: any) => {
utils.ensureNoExpected(expected, '.hasAssertions');
getState().isExpectingAssertions = true;
};
expect.assertions = assertions;
expect.hasAssertions = hasAssertions;
expect.getState = getState;
expect.setState = setState;
expect.extractExpectedAssertionsErrors = extractExpectedAssertionsErrors;
Expand Down

0 comments on commit 9b1e400

Please sign in to comment.