jest-doctor 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/utils/getStack.d.ts +2 -0
- package/dist/utils/getStack.js +11 -0
- package/dist/utils/getStack.js.map +1 -0
- package/package.json +68 -0
- package/readme.md +142 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const getStack = (stackFrom) => {
|
|
4
|
+
const error = {
|
|
5
|
+
stack: '',
|
|
6
|
+
};
|
|
7
|
+
Error.captureStackTrace(error, stackFrom);
|
|
8
|
+
return error.stack;
|
|
9
|
+
};
|
|
10
|
+
exports.default = getStack;
|
|
11
|
+
//# sourceMappingURL=getStack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getStack.js","sourceRoot":"","sources":["../../src/utils/getStack.ts"],"names":[],"mappings":";;AAAA,MAAM,QAAQ,GAAG,CAAC,SAAmB,EAAE,EAAE;IACvC,MAAM,KAAK,GAAG;QACZ,KAAK,EAAE,EAAE;KACV,CAAC;IAEF,KAAK,CAAC,iBAAiB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAE1C,OAAO,KAAK,CAAC,KAAK,CAAC;AACrB,CAAC,CAAC;AAEF,kBAAe,QAAQ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jest-doctor",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"description": "jest environment for leak detection",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"homepage": "https://github.com/stephan-dum/jest-doctor",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/stephan-dum/jest-doctor.git"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
"./package.json": "./package.json",
|
|
15
|
+
"./createEnvMixin": "./dist/createEnvMixin.js",
|
|
16
|
+
"./require": "./dist/requireEnvironment.js",
|
|
17
|
+
"./env/node": "./dist/env/node.js",
|
|
18
|
+
"./env/jsdom": "./dist/env/jsdom.js",
|
|
19
|
+
"./reporter": "./dist/reporter.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"readme.md"
|
|
24
|
+
],
|
|
25
|
+
"types": "./dist/types.d.ts",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@dev/eslint": "1.0.0",
|
|
28
|
+
"@dev/tsconfig": "1.0.0",
|
|
29
|
+
"@jest/reporters": "30.2.0",
|
|
30
|
+
"@swc/core": "^1.15.8",
|
|
31
|
+
"@swc/jest": "^0.2.39",
|
|
32
|
+
"@testing-library/dom": "^10.4.1",
|
|
33
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
34
|
+
"@testing-library/react": "^16.3.1",
|
|
35
|
+
"@types/cross-spawn": "^6.0.6",
|
|
36
|
+
"@types/jest": "30.0.0",
|
|
37
|
+
"@types/node": "24.3.3",
|
|
38
|
+
"@types/react": "^19.2.8",
|
|
39
|
+
"@types/react-dom": "^19.2.3",
|
|
40
|
+
"c8": "^10.1.3",
|
|
41
|
+
"cross-env": "^10.1.0",
|
|
42
|
+
"cross-spawn": "^7.0.6",
|
|
43
|
+
"eslint": "^9.39.2",
|
|
44
|
+
"istanbul-lib-report": "^3.0.1",
|
|
45
|
+
"jest": "30.2.0",
|
|
46
|
+
"memfs": "^4.52.0",
|
|
47
|
+
"nyc": "^17.1.0",
|
|
48
|
+
"react": "^19.2.3",
|
|
49
|
+
"react-dom": "^19.2.3",
|
|
50
|
+
"ts-jest": "^29.4.6",
|
|
51
|
+
"typescript": "5.9.2"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"chalk": "4.1.2",
|
|
55
|
+
"jest-environment-jsdom": "30.2.0",
|
|
56
|
+
"jest-environment-node": "30.2.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"typecheck": "tsc",
|
|
60
|
+
"build": "tsc --project tsconfig.build.json",
|
|
61
|
+
"dev": "yarn typecheck --watch & yarn build --watch",
|
|
62
|
+
"lint": "eslint .",
|
|
63
|
+
"test:unit": "jest --coverage",
|
|
64
|
+
"test:matrix": "node ./e2e/runMatrix.mjs",
|
|
65
|
+
"test": "yarn matrix && yarn test:unit && yarn coverage:merge",
|
|
66
|
+
"coverage:merge": "nyc report --reporter html --reporter text"
|
|
67
|
+
}
|
|
68
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# jest-doctor
|
|
2
|
+
|
|
3
|
+
**jest-doctor** is a custom Jest environment that **detects async leaks and test isolation bugs**.
|
|
4
|
+
|
|
5
|
+
It fails tests deterministically when they:
|
|
6
|
+
- Leave unresolved Promises
|
|
7
|
+
- leave open (fake) timers or intervals
|
|
8
|
+
- Use real timers or intervals and their total delay reach a certain threshold
|
|
9
|
+
- Emit console output
|
|
10
|
+
|
|
11
|
+
Jest-doctor is intentionally strict. If your test leaks async work, that is a bug, even if Jest normally ignores it.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install --save-dev jest-doctor
|
|
17
|
+
```
|
|
18
|
+
or
|
|
19
|
+
```bash
|
|
20
|
+
yarn add -D jest-doctor
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
Add one of the provided environments to your `jest.config.js`, in addition, the reporter provides a list ordered by severity and a total count overview.
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
export default {
|
|
29
|
+
testEnvironment: 'jest-doctor/env/node',
|
|
30
|
+
// optional
|
|
31
|
+
reporters: ['default', 'jest-doctor/reporter']
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Out-of-the-box jest-doctor supports node and jsdom environments. But you can also [build your own environment](./docs/build_your_own_environment.md).
|
|
36
|
+
|
|
37
|
+
### Configuration
|
|
38
|
+
|
|
39
|
+
The environment can be configured through jest config `testEnvironmentOptions`:
|
|
40
|
+
- **report**: an object defining which leaks should be tracked and reported
|
|
41
|
+
- **timers**: `false | 'warn' | 'trow'` (default `throw`) whether normal setTimeout and setInterval should be reported and how
|
|
42
|
+
- **fakeTimers**: `false | 'warn' | 'trow'` (default `throw`) same as timers but for fake api
|
|
43
|
+
- **promises**: `false` or object
|
|
44
|
+
- **onError**: `'warn' | 'trow'` (default `throw`) indicating if promises should be reported and how
|
|
45
|
+
- **patch**: `'async_hooks' | 'promise'` (default `async_hooks`) controls how to patch promises
|
|
46
|
+
- **async_hooks**: uses node async_hook API to detect any promise generation, robust solution but with an overhead.
|
|
47
|
+
- **promise**: overwrites the global Promise object which is faster but will not detect: internal promises, async await, native API promises!
|
|
48
|
+
- **console**: `false` or object (default object)
|
|
49
|
+
- **onError**: `'warn' | 'trow'` (default `throw`) how to handle reporting
|
|
50
|
+
- **methods**: `keyof Console` (default all methods) which console methods should be tracked
|
|
51
|
+
- **ignore**: `string | regexp | Array<string | regexp>` (default: []) allows to excluded console output from tracking
|
|
52
|
+
- **timerIsolation**: `'afterEach' | 'immediate'` (default: `'afterEach'`)
|
|
53
|
+
- **immediate**: report and clear timers directly after each test / hook block
|
|
54
|
+
- **afterEach**: `beforeAll`, `beforeEach` and `afterAll` are immediate but `test` and `afterEach` block defer reporting and cleanup until the last `afterEach` block is executed (or directly after the test if there are no `afterEach` blocks). This allows an easier clean up for example react testing framework registers an unmount function in an `afterEach` block to clean up.
|
|
55
|
+
- **delayThreshold**: `number` (default: `0`) the delay in milliseconds of all `setTimeout` and `setInterval` callback that get executed is add up. If this the sum is higher then the threshold at the end when reporting an error is throw, otherwise a warning is logged.
|
|
56
|
+
- **clearTimers**: `boolean` (default: `true`) whether to clear all timers base on `timerIsolation`
|
|
57
|
+
|
|
58
|
+
here is an example how the configuration could look like:
|
|
59
|
+
```js
|
|
60
|
+
export default {
|
|
61
|
+
testEnvironmentOptions: {
|
|
62
|
+
report: {
|
|
63
|
+
console: {
|
|
64
|
+
onError: 'warn',
|
|
65
|
+
methods: ["log", "warn", "error"],
|
|
66
|
+
ignore: /Third party message/,
|
|
67
|
+
},
|
|
68
|
+
timers: 'warn',
|
|
69
|
+
fakeTimers: 'warn',
|
|
70
|
+
promises: {
|
|
71
|
+
onErro: 'warn',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
delayThreshold: 1000,
|
|
75
|
+
timerIsolation: 'afterEach',
|
|
76
|
+
clearTimers: true,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
the reporter can be configured by the standard jest reporter config syntax
|
|
82
|
+
- **tmpDir**: `string` (default: `.tmp`) where to store reports from the environment to be read by the reporter
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
export default {
|
|
86
|
+
reporters: [
|
|
87
|
+
'default',
|
|
88
|
+
['jest-doctor/reporter', { tmpDir: 'custom-dir' }]
|
|
89
|
+
],
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Limitations
|
|
94
|
+
- it.concurrent is replaced with a sync version
|
|
95
|
+
- legacy fake timers are not mocked
|
|
96
|
+
- test and hook blocks do not support done callback or generators
|
|
97
|
+
- promises that resolve within the next tick cannot be tracked, for example:
|
|
98
|
+
```js
|
|
99
|
+
Promise.resolve().then(() => {
|
|
100
|
+
/* i am not tracked as unresolved */
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
- injectGlobals must be used for totalDelay and test attribution to work, because imports from jest can not be patched! Also this could give a wrong sense of confidence because one test could have open timers or promises that resolve while running other tests and are not present in the final report.
|
|
104
|
+
```js
|
|
105
|
+
import { expect, it, describe, beforeEach /*...*/ } from '@jest/globals';
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- console, setTimeout / setInterval can also be imported and will not participate in leak detection in these cases, but this can also serve as exit hatch if needed.
|
|
109
|
+
```js
|
|
110
|
+
import { setTimeout, setInterval } from 'node:timers';
|
|
111
|
+
import console from 'node:console';
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Recommendations
|
|
115
|
+
- use eslint to
|
|
116
|
+
- detect floating promises
|
|
117
|
+
- disallow setTimeout / setInterval in test files
|
|
118
|
+
- disallow console usage
|
|
119
|
+
- enable fake timers globally in config (be aware that there might be some issues ie axe needs real timers)
|
|
120
|
+
```js
|
|
121
|
+
afterEach(async () => {
|
|
122
|
+
jest.useRealTimers();
|
|
123
|
+
await axe();
|
|
124
|
+
jest.useFakeTimers();
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Tested against
|
|
129
|
+
- Jest 27, 28, 29, 30
|
|
130
|
+
- node 22, 24
|
|
131
|
+
|
|
132
|
+
# FAQ
|
|
133
|
+
|
|
134
|
+
### Why is jest-doctor so strict?
|
|
135
|
+
Because flaky tests cost more than broken builds.
|
|
136
|
+
|
|
137
|
+
### Does this slow tests down?
|
|
138
|
+
Slightly. Overhead is intentional and bounded.
|
|
139
|
+
|
|
140
|
+
### Why does console output fail tests?
|
|
141
|
+
It pollutes the console and is often a strong indicator that something is wrong.
|
|
142
|
+
Tests should always spy on console and assert on the output.
|