Skip to content

Commit fd7cf38

Browse files
authored
feat(testing): add ability to split jest tests (#22662)
1 parent 4cd1808 commit fd7cf38

File tree

9 files changed

+440
-636
lines changed

9 files changed

+440
-636
lines changed

docs/generated/packages/jest/documents/overview.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,54 @@ npm add -D @nx/jest
4040
{% /tab %}
4141
{% /tabs %}
4242

43+
#### Configuring @nx/jest/plugin for both E2E and Unit Tests
44+
45+
While Jest is most often used for unit tests, there are cases where it can be used for e2e tests as well as unit tests
46+
within the same workspace. In this case, you can configure the `@nx/jest/plugin` twice for the different cases.
47+
48+
```json {% fileName="nx.json" %}
49+
{
50+
"plugins": [
51+
{
52+
"plugin": "@nx/jest/plugin",
53+
"exclude": ["e2e/**/*"],
54+
"options": {
55+
"targetName": "test"
56+
}
57+
},
58+
{
59+
"plugin": "@nx/jest/plugin",
60+
"include": ["e2e/**/*"],
61+
"options": {
62+
"targetName": "e2e-local",
63+
"ciTargetName": "e2e-ci"
64+
}
65+
}
66+
]
67+
}
68+
```
69+
70+
### Splitting E2E Tests
71+
72+
If Jest is used to run E2E tests, you can enable [splitting the tasks](/ci/features/split-e2e-tasks) by file to get
73+
improved caching, distribution, and retrying flaky tests. Enable this, by providing a `ciTargetName`. This will create a
74+
target with that name which can be used in CI to run the tests for each file in a distributed fashion.
75+
76+
```json {% fileName="nx.json" %}
77+
{
78+
"plugins": [
79+
{
80+
"plugin": "@nx/jest/plugin",
81+
"include": ["e2e/**/*"],
82+
"options": {
83+
"targetName": "e2e-local",
84+
"ciTargetName": "e2e-ci"
85+
}
86+
}
87+
]
88+
}
89+
```
90+
4391
### How @nx/jest Infers Tasks
4492

4593
{% callout type="note" title="Inferred Tasks" %}

docs/nx-cloud/features/split-e2e-tasks.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ You could manually address these problems by splitting your e2e tests into small
1111

1212
## Set up
1313

14-
To enable automatically split e2e tasks, you need to turn on [inferred tasks](/concepts/inferred-tasks#existing-nx-workspaces) for the [@nx/cypress](/nx-api/cypress) or [@nx/playwright](/nx-api/playwright) plugins. Run this command to set up inferred tasks:
14+
To enable automatically split e2e tasks, you need to turn on [inferred tasks](/concepts/inferred-tasks#existing-nx-workspaces) for the [@nx/cypress](/nx-api/cypress), [@nx/playwright](/nx-api/playwright), or [@nx/playwright](/nx-api/jest) plugins. Run this command to set up inferred tasks:
1515

1616
{% tabs %}
1717
{% tab label="Cypress" %}
@@ -27,17 +27,25 @@ nx add @nx/cypress
2727
nx add @nx/playwright
2828
```
2929

30+
{% /tab %}
31+
{% tab label="Jest" %}
32+
33+
```shell {% skipRescope=true %}
34+
nx add @nx/jest
35+
```
36+
3037
{% /tab %}
3138
{% /tabs %}
3239

3340
This command will register the appropriate plugin in the `plugins` array of `nx.json`.
3441

3542
## Manual Configuration
3643

37-
If you are already using the `@nx/cypress` or `@nx/playwright` plugin, you need to manually add the appropriate configuration to the `plugins` array of `nx.json`. Follow the instructions for the plugin you are using:
44+
If you are already using the `@nx/cypress`, `@nx/playwright`, or `@nx/jest` plugin, you need to manually add the appropriate configuration to the `plugins` array of `nx.json`. Follow the instructions for the plugin you are using:
3845

3946
- [Configure Cypress Task Splitting](/nx-api/cypress#nxcypress-configuration)
4047
- [Configure Playwright Task Splitting](/nx-api/playwright#nxplaywright-configuration)
48+
- [Configure Jest Task Splitting](/nx-api/jest#splitting-e2e-tests)
4149

4250
## Usage
4351

docs/shared/packages/jest/jest-plugin.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,54 @@ npm add -D @nx/jest
4040
{% /tab %}
4141
{% /tabs %}
4242

43+
#### Configuring @nx/jest/plugin for both E2E and Unit Tests
44+
45+
While Jest is most often used for unit tests, there are cases where it can be used for e2e tests as well as unit tests
46+
within the same workspace. In this case, you can configure the `@nx/jest/plugin` twice for the different cases.
47+
48+
```json {% fileName="nx.json" %}
49+
{
50+
"plugins": [
51+
{
52+
"plugin": "@nx/jest/plugin",
53+
"exclude": ["e2e/**/*"],
54+
"options": {
55+
"targetName": "test"
56+
}
57+
},
58+
{
59+
"plugin": "@nx/jest/plugin",
60+
"include": ["e2e/**/*"],
61+
"options": {
62+
"targetName": "e2e-local",
63+
"ciTargetName": "e2e-ci"
64+
}
65+
}
66+
]
67+
}
68+
```
69+
70+
### Splitting E2E Tests
71+
72+
If Jest is used to run E2E tests, you can enable [splitting the tasks](/ci/features/split-e2e-tasks) by file to get
73+
improved caching, distribution, and retrying flaky tests. Enable this, by providing a `ciTargetName`. This will create a
74+
target with that name which can be used in CI to run the tests for each file in a distributed fashion.
75+
76+
```json {% fileName="nx.json" %}
77+
{
78+
"plugins": [
79+
{
80+
"plugin": "@nx/jest/plugin",
81+
"include": ["e2e/**/*"],
82+
"options": {
83+
"targetName": "e2e-local",
84+
"ciTargetName": "e2e-ci"
85+
}
86+
}
87+
]
88+
}
89+
```
90+
4391
### How @nx/jest Infers Tasks
4492

4593
{% callout type="note" title="Inferred Tasks" %}

e2e/jest/src/jest.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
runCLIAsync,
88
uniq,
99
updateFile,
10+
updateJson,
1011
} from '@nx/e2e/utils';
1112

1213
describe('Jest', () => {
@@ -147,4 +148,19 @@ describe('Jest', () => {
147148
'Test Suites: 1 passed, 1 total'
148149
);
149150
}, 90000);
151+
152+
it('should be able to run e2e tests split by tasks', async () => {
153+
const libName = uniq('lib');
154+
runCLI(`generate @nx/js:lib ${libName} --unitTestRunner=jest`);
155+
updateJson('nx.json', (json) => {
156+
const jestPlugin = json.plugins.find(
157+
(plugin) => plugin.plugin === '@nx/jest/plugin'
158+
);
159+
160+
jestPlugin.options.ciTargetName = 'e2e-ci';
161+
return json;
162+
});
163+
164+
await runCLIAsync(`e2e-ci ${libName}`);
165+
}, 90000);
150166
});

e2e/remix/tests/nx-remix.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ describe('Remix E2E Tests', () => {
104104
expect(result).toContain(`Successfully ran target test`);
105105

106106
const reactResult = runCLI(`test ${reactapp}`);
107-
expect(result).toContain(`Successfully ran target test`);
107+
expect(reactResult).toContain(`Successfully ran target test`);
108108
}, 120_000);
109109
});
110110

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@
206206
"jest-environment-jsdom": "29.4.3",
207207
"jest-environment-node": "^29.4.1",
208208
"jest-resolve": "^29.4.1",
209+
"jest-runtime": "^29.4.1",
209210
"jest-util": "^29.4.1",
210211
"js-tokens": "^4.0.0",
211212
"js-yaml": "4.1.0",

packages/jest/src/plugins/plugin.spec.ts

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ describe('@nx/jest/plugin', () => {
2424
};
2525

2626
await tempFs.createFiles({
27-
'proj/jest.config.js': '',
27+
'proj/jest.config.js': `module.exports = {}`,
28+
'proj/src/unit.spec.ts': '',
29+
'proj/src/ignore.spec.ts': '',
2830
'proj/project.json': '{}',
2931
});
3032
});
@@ -50,6 +52,7 @@ describe('@nx/jest/plugin', () => {
5052

5153
expect(nodes.projects.proj).toMatchInlineSnapshot(`
5254
{
55+
"metadata": undefined,
5356
"root": "proj",
5457
"targets": {
5558
"test": {
@@ -64,6 +67,122 @@ describe('@nx/jest/plugin', () => {
6467
],
6568
},
6669
],
70+
"metadata": {
71+
"description": "Run Jest Tests",
72+
"technologies": [
73+
"jest",
74+
],
75+
},
76+
"options": {
77+
"cwd": "proj",
78+
},
79+
"outputs": [
80+
"{workspaceRoot}/coverage",
81+
],
82+
},
83+
},
84+
}
85+
`);
86+
});
87+
88+
it('should create test-ci targets based on jest.config.ts', async () => {
89+
mockJestConfig(
90+
{
91+
coverageDirectory: '../coverage',
92+
testMatch: ['**/*.spec.ts'],
93+
testPathIgnorePatterns: ['ignore.spec.ts'],
94+
},
95+
context
96+
);
97+
const nodes = await createNodesFunction(
98+
'proj/jest.config.js',
99+
{
100+
targetName: 'test',
101+
ciTargetName: 'test-ci',
102+
},
103+
context
104+
);
105+
106+
expect(nodes.projects.proj).toMatchInlineSnapshot(`
107+
{
108+
"metadata": {
109+
"targetGroups": {
110+
"E2E (CI)": [
111+
"test-ci",
112+
"test-ci--src/unit.spec.ts",
113+
],
114+
},
115+
},
116+
"root": "proj",
117+
"targets": {
118+
"test": {
119+
"cache": true,
120+
"command": "jest",
121+
"inputs": [
122+
"default",
123+
"^production",
124+
{
125+
"externalDependencies": [
126+
"jest",
127+
],
128+
},
129+
],
130+
"metadata": {
131+
"description": "Run Jest Tests",
132+
"technologies": [
133+
"jest",
134+
],
135+
},
136+
"options": {
137+
"cwd": "proj",
138+
},
139+
"outputs": [
140+
"{workspaceRoot}/coverage",
141+
],
142+
},
143+
"test-ci": {
144+
"cache": true,
145+
"dependsOn": [
146+
"test-ci--src/unit.spec.ts",
147+
],
148+
"executor": "nx:noop",
149+
"inputs": [
150+
"default",
151+
"^production",
152+
{
153+
"externalDependencies": [
154+
"jest",
155+
],
156+
},
157+
],
158+
"metadata": {
159+
"description": "Run Jest Tests in CI",
160+
"technologies": [
161+
"jest",
162+
],
163+
},
164+
"outputs": [
165+
"{workspaceRoot}/coverage",
166+
],
167+
},
168+
"test-ci--src/unit.spec.ts": {
169+
"cache": true,
170+
"command": "jest src/unit.spec.ts",
171+
"inputs": [
172+
"default",
173+
"^production",
174+
{
175+
"externalDependencies": [
176+
"jest",
177+
],
178+
},
179+
],
180+
"metadata": {
181+
"description": "Run Jest Tests in src/unit.spec.ts",
182+
"technologies": [
183+
"jest",
184+
],
185+
},
67186
"options": {
68187
"cwd": "proj",
69188
},

0 commit comments

Comments
 (0)