Skip to content

Commit afc0cfb

Browse files
authored
feat: stringify Errors properly with --json flag (#15329)
1 parent c987cf8 commit afc0cfb

File tree

4 files changed

+93
-9
lines changed

4 files changed

+93
-9
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
- `[@jest/core]` Add `perfStats` to surface test setup overhead ([#14622](https://github.com/jestjs/jest/pull/14622))
2121
- `[@jest/core]` [**BREAKING**] Changed `--filter` to accept an object with shape `{ filtered: Array<string> }` to match [documentation](https://jestjs.io/docs/cli#--filterfile) ([#13319](https://github.com/jestjs/jest/pull/13319))
2222
- `[@jest/core]` Support `--outputFile` option for [`--listTests`](https://jestjs.io/docs/cli#--listtests) ([#14980](https://github.com/jestjs/jest/pull/14980))
23+
- `[@jest/core]` Stringify Errors properly with `--json` flag ([#15329](https://github.com/jestjs/jest/pull/15329))
2324
- `[@jest/core, @jest/test-sequencer]` [**BREAKING**] Exposes `globalConfig` & `contexts` to `TestSequencer` ([#14535](https://github.com/jestjs/jest/pull/14535), & [#14543](https://github.com/jestjs/jest/pull/14543))
25+
- `[jest-each]` Introduce `%$` option to add number of the test to its title ([#14710](https://github.com/jestjs/jest/pull/14710))
2426
- `[@jest/environment]` [**BREAKING**] Remove deprecated `jest.genMockFromModule()` ([#15042](https://github.com/jestjs/jest/pull/15042))
2527
- `[@jest/environment]` [**BREAKING**] Remove unnecessary defensive code ([#15045](https://github.com/jestjs/jest/pull/15045))
2628
- `[jest-environment-jsdom]` [**BREAKING**] Upgrade JSDOM to v22 ([#13825](https://github.com/jestjs/jest/pull/13825))
@@ -39,15 +41,14 @@
3941
- `[jest-runtime]` Support `import.meta.resolve` ([#14930](https://github.com/jestjs/jest/pull/14930))
4042
- `[jest-runtime]` [**BREAKING**] Make it mandatory to pass `globalConfig` to the `Runtime` constructor ([#15044](https://github.com/jestjs/jest/pull/15044))
4143
- `[jest-runtime]` Add `unstable_unmockModule` ([#15080](https://github.com/jestjs/jest/pull/15080))
44+
- `[jest-runtime]` Add `onGenerateMock` transformer callback for auto generated callbacks ([#15433](https://github.com/jestjs/jest/pull/15433))
4245
- `[@jest/schemas]` Upgrade `@sinclair/typebox` to v0.34 ([#15450](https://github.com/jestjs/jest/pull/15450))
4346
- `[@jest/types]` `test.each()`: Accept a readonly (`as const`) table properly ([#14565](https://github.com/jestjs/jest/pull/14565))
4447
- `[@jest/types]` Improve argument type inference passed to `test` and `describe` callback functions from `each` tables ([#14920](https://github.com/jestjs/jest/pull/14920))
4548
- `[jest-snapshot]` [**BREAKING**] Add support for [Error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) in snapshots ([#13965](https://github.com/facebook/jest/pull/13965))
4649
- `[jest-snapshot]` Support Prettier 3 ([#14566](https://github.com/facebook/jest/pull/14566))
4750
- `[@jest/util-snapshot]` Extract utils used by tooling from `jest-snapshot` into its own package ([#15095](https://github.com/facebook/jest/pull/15095))
4851
- `[pretty-format]` [**BREAKING**] Do not render empty string children (`''`) in React plugin ([#14470](https://github.com/facebook/jest/pull/14470))
49-
- `[jest-each]` Introduce `%$` option to add number of the test to its title ([#14710](https://github.com/jestjs/jest/pull/14710))
50-
- `[jest-runtime]` Add `onGenerateMock` transformer callback for auto generated callbacks ([#15433](https://github.com/jestjs/jest/pull/15433))
5152

5253
### Fixes
5354

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
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 serializeToJSON from '../serializeToJSON';
9+
10+
// populate an object with all basic JavaScript datatypes
11+
const object = {
12+
chillness: 100,
13+
flaws: null,
14+
hopOut: {
15+
atThe: 'after party',
16+
when: new Date('2000-07-14'),
17+
},
18+
i: ['pull up'],
19+
location: undefined,
20+
ok: true,
21+
species: 'capybara',
22+
weight: 9.5,
23+
};
24+
25+
it('serializes regular objects like JSON.stringify', () => {
26+
expect(serializeToJSON(object)).toEqual(JSON.stringify(object));
27+
});
28+
29+
it('serializes errors', () => {
30+
const objectWithError = {
31+
...object,
32+
error: new Error('too cool'),
33+
};
34+
const withError = serializeToJSON(objectWithError);
35+
const withoutError = JSON.stringify(objectWithError);
36+
37+
expect(withoutError).not.toEqual(withError);
38+
39+
expect(withError).toContain('"message":"too cool"');
40+
expect(withError).toContain('"name":"Error"');
41+
expect(withError).toContain('"stack":"Error:');
42+
43+
expect(JSON.parse(withError)).toMatchObject({
44+
error: {
45+
message: 'too cool',
46+
name: 'Error',
47+
},
48+
});
49+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
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 {isNativeError} from 'node:util/types';
9+
10+
/**
11+
* When we're asked to give a JSON output with the --json flag or otherwise,
12+
* some data we need to return don't serialize well with a basic
13+
* `JSON.stringify`, particularly Errors returned in `.openHandles`.
14+
*
15+
* This function handles the extended serialization wanted above.
16+
*/
17+
export default function serializeToJSON(
18+
value: unknown,
19+
space?: string | number,
20+
): string {
21+
return JSON.stringify(
22+
value,
23+
(_, value) => {
24+
// There might be more in Error, but pulling out just the message, name,
25+
// and stack should be good enough
26+
if (isNativeError(value)) {
27+
return {
28+
message: value.message,
29+
name: value.name,
30+
stack: value.stack,
31+
};
32+
}
33+
return value;
34+
},
35+
space,
36+
);
37+
}

packages/jest-core/src/runJest.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import collectNodeHandles, {
3333
type HandleCollectionResult,
3434
} from './collectHandles';
3535
import getNoTestsFoundMessage from './getNoTestsFoundMessage';
36+
import serializeToJSON from './lib/serializeToJSON';
3637
import runGlobalHook from './runGlobalHook';
3738
import type {Filter, TestRunData} from './types';
3839

@@ -111,21 +112,17 @@ const processResults = async (
111112
runResults = await processor(runResults);
112113
}
113114
if (isJSON) {
115+
const jsonString = serializeToJSON(formatTestResults(runResults));
114116
if (outputFile) {
115117
const cwd = tryRealpath(process.cwd());
116118
const filePath = path.resolve(cwd, outputFile);
117119

118-
fs.writeFileSync(
119-
filePath,
120-
`${JSON.stringify(formatTestResults(runResults))}\n`,
121-
);
120+
fs.writeFileSync(filePath, `${jsonString}\n`);
122121
outputStream.write(
123122
`Test results written to: ${path.relative(cwd, filePath)}\n`,
124123
);
125124
} else {
126-
process.stdout.write(
127-
`${JSON.stringify(formatTestResults(runResults))}\n`,
128-
);
125+
process.stdout.write(`${jsonString}\n`);
129126
}
130127
}
131128

0 commit comments

Comments
 (0)