Skip to content

Commit c95abca

Browse files
authored
feat: support concurrent in Jest Each (#9326)
1 parent f077751 commit c95abca

22 files changed

Lines changed: 649 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
### Features
44

55
- `[jest-circus, jest-jasmine2]` Include `failureDetails` property in test results ([#9496](https://github.com/facebook/jest/pull/9496))
6+
- `[jest-each, jest-jasmine, jest-circus]` Add support for .concurrent.each ([#9326](https://github.com/facebook/jest/pull/9326))
67

78
### Fixes
89

docs/GlobalAPI.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,171 @@ test('has lemon in it', () => {
465465

466466
Even though the call to `test` will return right away, the test doesn't complete until the promise resolves as well.
467467

468+
### `test.concurrent(name, fn, timeout)`
469+
470+
Also under the alias: `it.concurrent(name, fn, timeout)`
471+
472+
Use `test.concurrent` if you want the test to run concurrently.
473+
474+
> Note: `test.concurrent` is considered experimental - see [here])https://github.com/facebook/jest/labels/Area%3A%20Concurrent) for details on missing features and other issues
475+
476+
The first argument is the test name; the second argument is an asynchronous function that contains the expectations to test. The third argument (optional) is `timeout` (in milliseconds) for specifying how long to wait before aborting. _Note: The default timeout is 5 seconds._
477+
478+
```
479+
test.concurrent('addition of 2 numbers', async () => {
480+
expect(5 + 3).toBe(8);
481+
});
482+
483+
test.concurrent('subtraction 2 numbers', async () => {
484+
expect(5 - 3).toBe(2);
485+
});
486+
```
487+
488+
> Note: Use `maxConcurrency` in configuration to prevents Jest from executing more than the specified amount of tests at the same time
489+
490+
### `test.concurrent.each(table)(name, fn, timeout)`
491+
492+
Also under the alias: `it.concurrent.each(table)(name, fn, timeout)`
493+
494+
Use `test.concurrent.each` if you keep duplicating the same test with different data. `test.each` allows you to write the test once and pass data in, the tests are all run asynchronously.
495+
496+
`test.concurrent.each` is available with two APIs:
497+
498+
#### 1. `test.concurrent.each(table)(name, fn, timeout)`
499+
500+
- `table`: `Array` of Arrays with the arguments that are passed into the test `fn` for each row.
501+
- _Note_ If you pass in a 1D array of primitives, internally it will be mapped to a table i.e. `[1, 2, 3] -> [[1], [2], [3]]`
502+
- `name`: `String` the title of the test block.
503+
- Generate unique test titles by positionally injecting parameters with [`printf` formatting](https://nodejs.org/api/util.html#util_util_format_format_args):
504+
- `%p` - [pretty-format](https://www.npmjs.com/package/pretty-format).
505+
- `%s`- String.
506+
- `%d`- Number.
507+
- `%i` - Integer.
508+
- `%f` - Floating point value.
509+
- `%j` - JSON.
510+
- `%o` - Object.
511+
- `%#` - Index of the test case.
512+
- `%%` - single percent sign ('%'). This does not consume an argument.
513+
- `fn`: `Function` the test to be ran, this is the function that will receive the parameters in each row as function arguments, **this will have to be an asynchronous function**.
514+
- Optionally, you can provide a `timeout` (in milliseconds) for specifying how long to wait for each row before aborting. _Note: The default timeout is 5 seconds._
515+
516+
Example:
517+
518+
```js
519+
test.concurrent.each([
520+
[1, 1, 2],
521+
[1, 2, 3],
522+
[2, 1, 3],
523+
])('.add(%i, %i)', (a, b, expected) => {
524+
expect(a + b).toBe(expected);
525+
});
526+
```
527+
528+
#### 2. `` test.concurrent.each`table`(name, fn, timeout) ``
529+
530+
- `table`: `Tagged Template Literal`
531+
- First row of variable name column headings separated with `|`
532+
- One or more subsequent rows of data supplied as template literal expressions using `${value}` syntax.
533+
- `name`: `String` the title of the test, use `$variable` to inject test data into the test title from the tagged template expressions.
534+
- To inject nested object values use you can supply a keyPath i.e. `$variable.path.to.value`
535+
- `fn`: `Function` the test to be ran, this is the function that will receive the test data object, **this will have to be an asynchronous function**.
536+
- Optionally, you can provide a `timeout` (in milliseconds) for specifying how long to wait for each row before aborting. _Note: The default timeout is 5 seconds._
537+
538+
Example:
539+
540+
```js
541+
test.concurrent.each`
542+
a | b | expected
543+
${1} | ${1} | ${2}
544+
${1} | ${2} | ${3}
545+
${2} | ${1} | ${3}
546+
`('returns $expected when $a is added $b', ({a, b, expected}) => {
547+
expect(a + b).toBe(expected);
548+
});
549+
```
550+
551+
### `test.concurrent.only.each(table)(name, fn)`
552+
553+
Also under the alias: `it.concurrent.only.each(table)(name, fn)`
554+
555+
Use `test.concurrent.only.each` if you want to only run specific tests with different test data concurrently.
556+
557+
`test.concurrent.only.each` is available with two APIs:
558+
559+
#### `test.concurrent.only.each(table)(name, fn)`
560+
561+
```js
562+
test.concurrent.only.each([
563+
[1, 1, 2],
564+
[1, 2, 3],
565+
[2, 1, 3],
566+
])('.add(%i, %i)', async (a, b, expected) => {
567+
expect(a + b).toBe(expected);
568+
});
569+
570+
test('will not be ran', () => {
571+
expect(1 / 0).toBe(Infinity);
572+
});
573+
```
574+
575+
#### `` test.only.each`table`(name, fn) ``
576+
577+
```js
578+
test.concurrent.only.each`
579+
a | b | expected
580+
${1} | ${1} | ${2}
581+
${1} | ${2} | ${3}
582+
${2} | ${1} | ${3}
583+
`('returns $expected when $a is added $b', async ({a, b, expected}) => {
584+
expect(a + b).toBe(expected);
585+
});
586+
587+
test('will not be ran', () => {
588+
expect(1 / 0).toBe(Infinity);
589+
});
590+
```
591+
592+
### `test.concurrent.skip.each(table)(name, fn)`
593+
594+
Also under the alias: `it.concurrent.skip.each(table)(name, fn)`
595+
596+
Use `test.concurrent.skip.each` if you want to stop running a collection of asynchronous data driven tests.
597+
598+
`test.concurrent.skip.each` is available with two APIs:
599+
600+
#### `test.concurrent.skip.each(table)(name, fn)`
601+
602+
```js
603+
test.concurrent.skip.each([
604+
[1, 1, 2],
605+
[1, 2, 3],
606+
[2, 1, 3],
607+
])('.add(%i, %i)', async (a, b, expected) => {
608+
expect(a + b).toBe(expected); // will not be ran
609+
});
610+
611+
test('will be ran', () => {
612+
expect(1 / 0).toBe(Infinity);
613+
});
614+
```
615+
616+
#### `` test.concurrent.skip.each`table`(name, fn) ``
617+
618+
```js
619+
test.concurrent.skip.each`
620+
a | b | expected
621+
${1} | ${1} | ${2}
622+
${1} | ${2} | ${3}
623+
${2} | ${1} | ${3}
624+
`('returns $expected when $a is added $b', async ({a, b, expected}) => {
625+
expect(a + b).toBe(expected); // will not be ran
626+
});
627+
628+
test('will be ran', () => {
629+
expect(1 / 0).toBe(Infinity);
630+
});
631+
```
632+
468633
### `test.each(table)(name, fn, timeout)`
469634

470635
Also under the alias: `it.each(table)(name, fn)` and `` it.each`table`(name, fn) ``
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
import {json as runWithJson} from '../runJest';
8+
9+
it('works with concurrent.each', () => {
10+
const {json} = runWithJson('circus-concurrent', [
11+
'concurrent-each.test.js',
12+
'--testRunner=jest-circus/runner',
13+
]);
14+
expect(json.numTotalTests).toBe(4);
15+
expect(json.numPassedTests).toBe(2);
16+
expect(json.numFailedTests).toBe(0);
17+
expect(json.numPendingTests).toBe(2);
18+
});
19+
20+
it('works with concurrent.only.each', () => {
21+
const {json} = runWithJson('circus-concurrent', [
22+
'concurrent-only-each.test.js',
23+
'--testRunner=jest-circus/runner',
24+
]);
25+
expect(json.numTotalTests).toBe(4);
26+
expect(json.numPassedTests).toBe(2);
27+
expect(json.numFailedTests).toBe(0);
28+
expect(json.numPendingTests).toBe(2);
29+
});

e2e/__tests__/jasmineAsync.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,24 @@ describe('async jasmine', () => {
128128
expect(json.testResults[0].message).toMatch(/concurrent test fails/);
129129
});
130130

131+
it('works with concurrent.each', () => {
132+
const {json} = runWithJson('jasmine-async', ['concurrent-each.test.js']);
133+
expect(json.numTotalTests).toBe(4);
134+
expect(json.numPassedTests).toBe(2);
135+
expect(json.numFailedTests).toBe(0);
136+
expect(json.numPendingTests).toBe(2);
137+
});
138+
139+
it('works with concurrent.only.each', () => {
140+
const {json} = runWithJson('jasmine-async', [
141+
'concurrent-only-each.test.js',
142+
]);
143+
expect(json.numTotalTests).toBe(4);
144+
expect(json.numPassedTests).toBe(2);
145+
expect(json.numFailedTests).toBe(0);
146+
expect(json.numPendingTests).toBe(2);
147+
});
148+
131149
it("doesn't execute more than 5 tests simultaneously", () => {
132150
const {json} = runWithJson('jasmine-async', ['concurrent-many.test.js']);
133151
expect(json.numTotalTests).toBe(10);
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+
'use strict';
9+
10+
it.concurrent.each([
11+
[1, 2],
12+
[2, 3],
13+
])('adds one to number', async (a, b) => {
14+
expect(a + 1).toBe(b);
15+
});
16+
17+
it.concurrent.skip.each([
18+
[1, 2],
19+
[2, 3],
20+
])('should skip this test', Promise.resolve());
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
'use strict';
9+
10+
it.concurrent.only.each([
11+
[1, 2],
12+
[2, 3],
13+
])('adds one to number', async (a, b) => {
14+
expect(a + 1).toBe(b);
15+
});
16+
17+
it.concurrent.each([
18+
[1, 2],
19+
[2, 3],
20+
])('adds one to number', async (a, b) => {
21+
expect(a + 1).toBe(b);
22+
});

e2e/circus-concurrent/package.json

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+
}
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+
'use strict';
9+
10+
it.concurrent.each([
11+
[1, 2],
12+
[2, 3],
13+
])('adds one to number', async (a, b) => {
14+
expect(a + 1).toBe(b);
15+
});
16+
17+
it.concurrent.skip.each([
18+
[1, 2],
19+
[2, 3],
20+
])('should skip this test', Promise.resolve());
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
'use strict';
9+
10+
it.concurrent.only.each([
11+
[1, 2],
12+
[2, 3],
13+
])('adds one to number', async (a, b) => {
14+
expect(a + 1).toBe(b);
15+
});
16+
17+
it.concurrent.each([
18+
[1, 2],
19+
[2, 3],
20+
])('adds one to number', async (a, b) => {
21+
expect(a + 1).toBe(b);
22+
});

packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
addSerializer,
2323
buildSnapshotResolver,
2424
} from 'jest-snapshot';
25+
import {bind} from 'jest-each';
2526
import throat from 'throat';
2627
import {
2728
ROOT_DESCRIBE_BLOCK_NAME,
@@ -77,7 +78,7 @@ export const initialize = async ({
7778
nodeGlobal.test.concurrent = (test => {
7879
const concurrent = (
7980
testName: string,
80-
testFn: () => Promise<any>,
81+
testFn: () => Promise<unknown>,
8182
timeout?: number,
8283
) => {
8384
// For concurrent tests we first run the function that returns promise, and then register a
@@ -90,18 +91,23 @@ export const initialize = async ({
9091
nodeGlobal.test(testName, () => promise, timeout);
9192
};
9293

93-
concurrent.only = (
94+
const only = (
9495
testName: string,
95-
testFn: () => Promise<any>,
96+
testFn: () => Promise<unknown>,
9697
timeout?: number,
9798
) => {
9899
const promise = mutex(() => testFn());
99100
// eslint-disable-next-line jest/no-focused-tests
100101
test.only(testName, () => promise, timeout);
101102
};
102103

104+
concurrent.only = only;
103105
concurrent.skip = test.skip;
104106

107+
concurrent.each = bind(test, false);
108+
concurrent.skip.each = bind(test.skip, false);
109+
only.each = bind(test.only, false);
110+
105111
return concurrent;
106112
})(nodeGlobal.test);
107113

0 commit comments

Comments
 (0)