Skip to content

Commit 7a3c997

Browse files
authored
jest-circus: throw if a test / hook is defined asynchronously (#8096)
1 parent 42f920c commit 7a3c997

File tree

9 files changed

+134
-3
lines changed

9 files changed

+134
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
- `[jest-circus, jest-console, jest-jasmine2, jest-reporters, jest-util, pretty-format]` Fix time durating formatting and consolidate time formatting code ([#9765](https://github.com/facebook/jest/pull/9765))
1010
- `[jest-circus]` [**BREAKING**] Fail tests if a test takes a done callback and have return values ([#9129](https://github.com/facebook/jest/pull/9129))
11+
- `[jest-circus]` [**BREAKING**] Throw a proper error if a test / hook is defined asynchronously ([#8096](https://github.com/facebook/jest/pull/8096))
1112
- `[jest-config, jest-resolve]` [**BREAKING**] Remove support for `browser` field ([#9943](https://github.com/facebook/jest/pull/9943))
1213

1314
### Chore & Maintenance
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`defining tests and hooks asynchronously throws 1`] = `
4+
FAIL __tests__/asyncDefinition.test.js
5+
6+
7+
● Test suite failed to run
8+
9+
Cannot add a test after tests have started running. Tests must be defined synchronously.
10+
11+
10 |
12+
11 | Promise.resolve().then(() => {
13+
> 12 | test('async definition inside describe', () => {});
14+
| ^
15+
13 | afterAll(() => {});
16+
14 | });
17+
15 | });
18+
19+
at test (__tests__/asyncDefinition.test.js:12:5)
20+
21+
● Test suite failed to run
22+
23+
Cannot add a hook after tests have started running. Hooks must be defined synchronously.
24+
25+
11 | Promise.resolve().then(() => {
26+
12 | test('async definition inside describe', () => {});
27+
> 13 | afterAll(() => {});
28+
| ^
29+
14 | });
30+
15 | });
31+
16 |
32+
33+
at afterAll (__tests__/asyncDefinition.test.js:13:5)
34+
35+
● Test suite failed to run
36+
37+
Cannot add a test after tests have started running. Tests must be defined synchronously.
38+
39+
16 |
40+
17 | Promise.resolve().then(() => {
41+
> 18 | test('async definition outside describe', () => {});
42+
| ^
43+
19 | afterAll(() => {});
44+
20 | });
45+
21 |
46+
47+
at test (__tests__/asyncDefinition.test.js:18:3)
48+
49+
● Test suite failed to run
50+
51+
Cannot add a hook after tests have started running. Hooks must be defined synchronously.
52+
53+
17 | Promise.resolve().then(() => {
54+
18 | test('async definition outside describe', () => {});
55+
> 19 | afterAll(() => {});
56+
| ^
57+
20 | });
58+
21 |
59+
60+
at afterAll (__tests__/asyncDefinition.test.js:19:3)
61+
`;

e2e/__tests__/beforeEachQueue.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
import {skipSuiteOnJestCircus} from '@jest/test-utils';
89
import {wrap} from 'jest-snapshot-serializer-raw';
910
import runJest from '../runJest';
1011

12+
skipSuiteOnJestCircus(); // Circus does not support funky async definitions
13+
1114
describe('Correct beforeEach order', () => {
1215
it('ensures the correct order for beforeEach', () => {
1316
const result = runJest('before-each-queue');
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {skipSuiteOnJasmine} from '@jest/test-utils';
9+
import {wrap} from 'jest-snapshot-serializer-raw';
10+
import {extractSummary} from '../Utils';
11+
import runJest from '../runJest';
12+
13+
skipSuiteOnJasmine();
14+
15+
it('defining tests and hooks asynchronously throws', () => {
16+
const result = runJest('circus-declaration-errors', [
17+
'asyncDefinition.test.js',
18+
]);
19+
20+
expect(result.exitCode).toBe(1);
21+
22+
const {rest} = extractSummary(result.stderr);
23+
expect(wrap(rest)).toMatchSnapshot();
24+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
describe('describe', () => {
9+
test('correct test def', () => {});
10+
11+
Promise.resolve().then(() => {
12+
test('async definition inside describe', () => {});
13+
afterAll(() => {});
14+
});
15+
});
16+
17+
Promise.resolve().then(() => {
18+
test('async definition outside describe', () => {});
19+
afterAll(() => {});
20+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"jest": {
3+
"testEnvironment": "node"
4+
}
5+
}

packages/jest-circus/src/eventHandler.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,21 +88,35 @@ const eventHandler: Circus.EventHandler = (
8888
break;
8989
}
9090
case 'add_hook': {
91-
const {currentDescribeBlock} = state;
91+
const {currentDescribeBlock, hasStarted} = state;
9292
const {asyncError, fn, hookType: type, timeout} = event;
9393
const parent = currentDescribeBlock;
94+
95+
if (hasStarted) {
96+
asyncError.message =
97+
'Cannot add a hook after tests have started running. Hooks must be defined synchronously.';
98+
state.unhandledErrors.push(asyncError);
99+
break;
100+
}
101+
94102
currentDescribeBlock.hooks.push({asyncError, fn, parent, timeout, type});
95103
break;
96104
}
97105
case 'add_test': {
98-
const {currentDescribeBlock, currentlyRunningTest} = state;
106+
const {currentDescribeBlock, currentlyRunningTest, hasStarted} = state;
99107
const {asyncError, fn, mode, testName: name, timeout} = event;
100108

101109
if (currentlyRunningTest) {
102110
throw new Error(
103111
`Tests cannot be nested. Test "${name}" cannot run because it is nested within "${currentlyRunningTest.name}".`,
104112
);
105113
}
114+
if (hasStarted) {
115+
asyncError.message =
116+
'Cannot add a test after tests have started running. Tests must be defined synchronously.';
117+
state.unhandledErrors.push(asyncError);
118+
break;
119+
}
106120

107121
const test = makeTest(
108122
fn,
@@ -168,6 +182,7 @@ const eventHandler: Circus.EventHandler = (
168182
break;
169183
}
170184
case 'run_start': {
185+
state.hasStarted = true;
171186
global[TEST_TIMEOUT_SYMBOL] &&
172187
(state.testTimeout = global[TEST_TIMEOUT_SYMBOL]);
173188
break;

packages/jest-circus/src/state.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ const INITIAL_STATE: Circus.State = {
2424
currentDescribeBlock: ROOT_DESCRIBE_BLOCK,
2525
currentlyRunningTest: null,
2626
expand: undefined,
27-
hasFocusedTests: false, // whether .only has been used on any test/describe
27+
hasFocusedTests: false,
28+
hasStarted: false,
2829
includeTestLocationInResult: false,
2930
parentProcess: null,
3031
rootDescribeBlock: ROOT_DESCRIBE_BLOCK,

packages/jest-types/src/Circus.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export type State = {
187187
currentlyRunningTest?: TestEntry | null; // including when hooks are being executed
188188
expand?: boolean; // expand error messages
189189
hasFocusedTests: boolean; // that are defined using test.only
190+
hasStarted: boolean; // whether the rootDescribeBlock has started running
190191
// Store process error handlers. During the run we inject our own
191192
// handlers (so we could fail tests on unhandled errors) and later restore
192193
// the original ones.

0 commit comments

Comments
 (0)