Skip to content

Commit

Permalink
Prevent false test failures caused by promise rejections handled asyn…
Browse files Browse the repository at this point in the history
…chronously (#14110)
  • Loading branch information
stekycz committed Jun 21, 2023
1 parent 2cc0258 commit 57e1d4e
Show file tree
Hide file tree
Showing 28 changed files with 771 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -9,6 +9,7 @@

### Fixes

- `[jest-circus]` Prevent false test failures caused by promise rejections handled asynchronously ([#14110](https://github.com/jestjs/jest/pull/14110))
- `[jest-config]` Handle frozen config object ([#14054](https://github.com/facebook/jest/pull/14054))
- `[jest-config]` Allow `coverageDirectory` and `collectCoverageFrom` in project config ([#14180](https://github.com/jestjs/jest/pull/14180))
- `[jest-core]` Always use workers in watch mode to avoid crashes ([#14059](https://github.com/facebook/jest/pull/14059)).
Expand Down
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`prints useful error for environment methods after test is done 1`] = `
"ReferenceError: You are trying to access a property or method of the Jest environment after it has been torn down. From __tests__/afterTeardown.test.js.
" ReferenceError: You are trying to access a property or method of the Jest environment outside of the scope of the test code.
9 | test('access environment methods after done', () => {
10 | setTimeout(() => {
Expand Down
@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`prints useful error for environment methods after test is done 1`] = `
"ReferenceError: You are trying to access a property or method of the Jest environment after it has been torn down. From __tests__/afterTeardown.test.js.
9 | test('access environment methods after done', () => {
10 | setTimeout(() => {
> 11 | jest.clearAllTimers();
| ^
12 | }, 0);
13 | });
14 |"
`;
236 changes: 236 additions & 0 deletions e2e/__tests__/__snapshots__/promiseAsyncHandling.test.ts.snap
@@ -0,0 +1,236 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`fails because of unhandled promise rejection in afterAll hook 1`] = `
Object {
"rest": "FAIL __tests__/unhandledRejectionAfterAll.test.js
Test suite failed to run
REJECTED
11 |
12 | afterAll(async () => {
> 13 | Promise.reject(new Error('REJECTED'));
| ^
14 |
15 | await promisify(setTimeout)(0);
16 | });
at Object.<anonymous> (__tests__/unhandledRejectionAfterAll.test.js:13:18)",
"summary": "Test Suites: 1 failed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /unhandledRejectionAfterAll.test.js/i.",
}
`;
exports[`fails because of unhandled promise rejection in afterEach hook 1`] = `
Object {
"rest": "FAIL __tests__/unhandledRejectionAfterEach.test.js
foo #1
foo #2
foo #1
REJECTED
11 |
12 | afterEach(async () => {
> 13 | Promise.reject(new Error('REJECTED'));
| ^
14 |
15 | await promisify(setTimeout)(0);
16 | });
at Object.<anonymous> (__tests__/unhandledRejectionAfterEach.test.js:13:18)
● foo #2
REJECTED
11 |
12 | afterEach(async () => {
> 13 | Promise.reject(new Error('REJECTED'));
| ^
14 |
15 | await promisify(setTimeout)(0);
16 | });
at Object.<anonymous> (__tests__/unhandledRejectionAfterEach.test.js:13:18)",
"summary": "Test Suites: 1 failed, 1 total
Tests: 2 failed, 2 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /unhandledRejectionAfterEach.test.js/i.",
}
`;
exports[`fails because of unhandled promise rejection in beforeAll hook 1`] = `
Object {
"rest": "FAIL __tests__/unhandledRejectionBeforeAll.test.js
foo
foo
REJECTED
11 |
12 | beforeAll(async () => {
> 13 | Promise.reject(new Error('REJECTED'));
| ^
14 |
15 | await promisify(setTimeout)(0);
16 | });
at Object.<anonymous> (__tests__/unhandledRejectionBeforeAll.test.js:13:18)",
"summary": "Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /unhandledRejectionBeforeAll.test.js/i.",
}
`;
exports[`fails because of unhandled promise rejection in beforeEach hook 1`] = `
Object {
"rest": "FAIL __tests__/unhandledRejectionBeforeEach.test.js
foo #1
foo #2
foo #1
REJECTED
11 |
12 | beforeEach(async () => {
> 13 | Promise.reject(new Error('REJECTED'));
| ^
14 |
15 | await promisify(setTimeout)(0);
16 | });
at Object.<anonymous> (__tests__/unhandledRejectionBeforeEach.test.js:13:18)
● foo #2
REJECTED
11 |
12 | beforeEach(async () => {
> 13 | Promise.reject(new Error('REJECTED'));
| ^
14 |
15 | await promisify(setTimeout)(0);
16 | });
at Object.<anonymous> (__tests__/unhandledRejectionBeforeEach.test.js:13:18)",
"summary": "Test Suites: 1 failed, 1 total
Tests: 2 failed, 2 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /unhandledRejectionBeforeEach.test.js/i.",
}
`;
exports[`fails because of unhandled promise rejection in test 1`] = `
Object {
"rest": "FAIL __tests__/unhandledRejectionTest.test.js
w/o event loop turn after rejection
w/ event loop turn after rejection in async function
✕ w/ event loop turn after rejection in sync function
✕ combined w/ another failure _after_ promise rejection
● w/o event loop turn after rejection
REJECTED
11 |
12 | test('w/o event loop turn after rejection', () => {
> 13 | Promise.reject(new Error('REJECTED'));
| ^
14 | });
15 |
16 | test('w/ event loop turn after rejection in async function', async () => {
at Object.<anonymous> (__tests__/unhandledRejectionTest.test.js:13:18)
● w/ event loop turn after rejection in async function
REJECTED
15 |
16 | test('w/ event loop turn after rejection in async function', async () => {
> 17 | Promise.reject(new Error('REJECTED'));
| ^
18 |
19 | await promisify(setTimeout)(0);
20 | });
at Object.<anonymous> (__tests__/unhandledRejectionTest.test.js:17:18)
● w/ event loop turn after rejection in sync function
REJECTED
21 |
22 | test('w/ event loop turn after rejection in sync function', done => {
> 23 | Promise.reject(new Error('REJECTED'));
| ^
24 |
25 | setTimeout(done, 0);
26 | });
at Object.<anonymous> (__tests__/unhandledRejectionTest.test.js:23:18)
● combined w/ another failure _after_ promise rejection
expect(received).toBe(expected) // Object.is equality
Expected: false
Received: true
31 | await promisify(setTimeout)(0);
32 |
> 33 | expect(true).toBe(false);
| ^
34 | });
35 |
at Object.toBe (__tests__/unhandledRejectionTest.test.js:33:16)
● combined w/ another failure _after_ promise rejection
REJECTED
27 |
28 | test('combined w/ another failure _after_ promise rejection', async () => {
> 29 | Promise.reject(new Error('REJECTED'));
| ^
30 |
31 | await promisify(setTimeout)(0);
32 |
at Object.<anonymous> (__tests__/unhandledRejectionTest.test.js:29:18)",
"summary": "Test Suites: 1 failed, 1 total
Tests: 4 failed, 4 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /unhandledRejectionTest.test.js/i.",
}
`;
exports[`succeeds for async handled promise rejections 1`] = `
Object {
"rest": "PASS __tests__/rejectionHandled.test.js
async function succeeds because the promise is eventually awaited by assertion
async function succeeds because the promise is eventually directly awaited
✓ sync function succeeds because the promise is eventually handled by \`.catch\` handler",
"summary": "Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /rejectionHandled.test.js/i.",
}
`;
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`prints useful error for requires after test is done 1`] = `
"ReferenceError: You are trying to \`import\` a file after the Jest environment has been torn down. From __tests__/lateRequire.test.js.
" ReferenceError: You are trying to \`import\` a file outside of the scope of the test code.
9 | test('require after done', () => {
10 | setTimeout(() => {
Expand Down
@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`prints useful error for requires after test is done 1`] = `
"ReferenceError: You are trying to \`import\` a file after the Jest environment has been torn down. From __tests__/lateRequire.test.js.
9 | test('require after done', () => {
10 | setTimeout(() => {
> 11 | const double = require('../');
| ^
12 |
13 | expect(double(5)).toBe(10);
14 | }, 0);"
`;
9 changes: 6 additions & 3 deletions e2e/__tests__/environmentAfterTeardown.test.ts
Expand Up @@ -5,14 +5,17 @@
* LICENSE file in the root directory of this source tree.
*/

import {skipSuiteOnJasmine} from '@jest/test-utils';
import runJest from '../runJest';

skipSuiteOnJasmine();

test('prints useful error for environment methods after test is done', () => {
const {stderr} = runJest('environment-after-teardown');
const interestingLines = stderr.split('\n').slice(9, 18).join('\n');
const interestingLines = stderr.split('\n').slice(5, 14).join('\n');

expect(interestingLines).toMatchSnapshot();
expect(stderr.split('\n')[9]).toBe(
'ReferenceError: You are trying to access a property or method of the Jest environment after it has been torn down. From __tests__/afterTeardown.test.js.',
expect(stderr.split('\n')[5]).toMatch(
'ReferenceError: You are trying to access a property or method of the Jest environment outside of the scope of the test code.',
);
});
21 changes: 21 additions & 0 deletions e2e/__tests__/environmentAfterTeardownJasmine.test.ts
@@ -0,0 +1,21 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {skipSuiteOnJestCircus} from '@jest/test-utils';
import runJest from '../runJest';

skipSuiteOnJestCircus();

test('prints useful error for environment methods after test is done', () => {
const {stderr} = runJest('environment-after-teardown');
const interestingLines = stderr.split('\n').slice(9, 18).join('\n');

expect(interestingLines).toMatchSnapshot();
expect(stderr.split('\n')[9]).toBe(
'ReferenceError: You are trying to access a property or method of the Jest environment after it has been torn down. From __tests__/afterTeardown.test.js.',
);
});
6 changes: 5 additions & 1 deletion e2e/__tests__/fakeTimersLegacy.test.ts
Expand Up @@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import {isJestJasmineRun} from '@jest/test-utils';
import runJest from '../runJest';

describe('enableGlobally', () => {
Expand Down Expand Up @@ -39,10 +40,13 @@ describe('requestAnimationFrame', () => {

describe('setImmediate', () => {
test('fakes setImmediate', () => {
// Jasmine runner does not handle unhandled promise rejections that are causing the test to fail in Jest circus
const expectedExitCode = isJestJasmineRun() ? 0 : 1;

const result = runJest('fake-timers-legacy/set-immediate');

expect(result.stderr).toMatch('setImmediate test');
expect(result.exitCode).toBe(0);
expect(result.exitCode).toBe(expectedExitCode);
});
});

Expand Down

0 comments on commit 57e1d4e

Please sign in to comment.