Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: stringify Errors properly with --json flag #15329

Merged
merged 6 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
- `[@jest/core]` Add `perfStats` to surface test setup overhead ([#14622](https://github.com/jestjs/jest/pull/14622))
- `[@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))
- `[@jest/core]` Support `--outputFile` option for [`--listTests`](https://jestjs.io/docs/cli#--listtests) ([#14980](https://github.com/jestjs/jest/pull/14980))
- `[@jest/core]` Stringify Errors properly with `--json` flag ([#15329](https://github.com/jestjs/jest/pull/15329))
- `[@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))
- `[jest-each]` Introduce `%$` option to add number of the test to its title ([#14710](https://github.com/jestjs/jest/pull/14710))
- `[@jest/environment]` [**BREAKING**] Remove deprecated `jest.genMockFromModule()` ([#15042](https://github.com/jestjs/jest/pull/15042))
- `[@jest/environment]` [**BREAKING**] Remove unnecessary defensive code ([#15045](https://github.com/jestjs/jest/pull/15045))
- `[jest-environment-jsdom]` [**BREAKING**] Upgrade JSDOM to v22 ([#13825](https://github.com/jestjs/jest/pull/13825))
Expand All @@ -39,15 +41,14 @@
- `[jest-runtime]` Support `import.meta.resolve` ([#14930](https://github.com/jestjs/jest/pull/14930))
- `[jest-runtime]` [**BREAKING**] Make it mandatory to pass `globalConfig` to the `Runtime` constructor ([#15044](https://github.com/jestjs/jest/pull/15044))
- `[jest-runtime]` Add `unstable_unmockModule` ([#15080](https://github.com/jestjs/jest/pull/15080))
- `[jest-runtime]` Add `onGenerateMock` transformer callback for auto generated callbacks ([#15433](https://github.com/jestjs/jest/pull/15433))
- `[@jest/schemas]` Upgrade `@sinclair/typebox` to v0.34 ([#15450](https://github.com/jestjs/jest/pull/15450))
- `[@jest/types]` `test.each()`: Accept a readonly (`as const`) table properly ([#14565](https://github.com/jestjs/jest/pull/14565))
- `[@jest/types]` Improve argument type inference passed to `test` and `describe` callback functions from `each` tables ([#14920](https://github.com/jestjs/jest/pull/14920))
- `[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))
- `[jest-snapshot]` Support Prettier 3 ([#14566](https://github.com/facebook/jest/pull/14566))
- `[@jest/util-snapshot]` Extract utils used by tooling from `jest-snapshot` into its own package ([#15095](https://github.com/facebook/jest/pull/15095))
- `[pretty-format]` [**BREAKING**] Do not render empty string children (`''`) in React plugin ([#14470](https://github.com/facebook/jest/pull/14470))
- `[jest-each]` Introduce `%$` option to add number of the test to its title ([#14710](https://github.com/jestjs/jest/pull/14710))
- `[jest-runtime]` Add `onGenerateMock` transformer callback for auto generated callbacks ([#15433](https://github.com/jestjs/jest/pull/15433))

### Fixes

Expand Down
49 changes: 49 additions & 0 deletions packages/jest-core/src/lib/__tests__/serializeToJSON.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import serializeToJSON from '../serializeToJSON';

// populate an object with all basic JavaScript datatypes
const object = {
chillness: 100,
flaws: null,
hopOut: {
atThe: 'after party',
when: new Date('2000-07-14'),
},
i: ['pull up'],
location: undefined,
ok: true,
species: 'capybara',
weight: 9.5,
};

it('serializes regular objects like JSON.stringify', () => {
expect(serializeToJSON(object)).toEqual(JSON.stringify(object));
});

it('serializes errors', () => {
const objectWithError = {
...object,
error: new Error('too cool'),
};
const withError = serializeToJSON(objectWithError);
const withoutError = JSON.stringify(objectWithError);

expect(withoutError).not.toEqual(withError);

expect(withError).toContain('"message":"too cool"');
expect(withError).toContain('"name":"Error"');
expect(withError).toContain('"stack":"Error:');

expect(JSON.parse(withError)).toMatchObject({
error: {
message: 'too cool',
name: 'Error',
},
});
});
37 changes: 37 additions & 0 deletions packages/jest-core/src/lib/serializeToJSON.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {isNativeError} from 'node:util/types';

/**
* When we're asked to give a JSON output with the --json flag or otherwise,
* some data we need to return don't serialize well with a basic
* `JSON.stringify`, particularly Errors returned in `.openHandles`.
*
* This function handles the extended serialization wanted above.
*/
export default function serializeToJSON(
value: unknown,
space?: string | number,
): string {
return JSON.stringify(
value,
(_, value) => {
// There might be more in Error, but pulling out just the message, name,
// and stack should be good enough
if (isNativeError(value)) {
return {
message: value.message,
name: value.name,
stack: value.stack,
};
}
return value;
},
space,
);
}
11 changes: 4 additions & 7 deletions packages/jest-core/src/runJest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import collectNodeHandles, {
type HandleCollectionResult,
} from './collectHandles';
import getNoTestsFoundMessage from './getNoTestsFoundMessage';
import serializeToJSON from './lib/serializeToJSON';
import runGlobalHook from './runGlobalHook';
import type {Filter, TestRunData} from './types';

Expand Down Expand Up @@ -111,21 +112,17 @@ const processResults = async (
runResults = await processor(runResults);
}
if (isJSON) {
const jsonString = serializeToJSON(formatTestResults(runResults));
if (outputFile) {
const cwd = tryRealpath(process.cwd());
const filePath = path.resolve(cwd, outputFile);

fs.writeFileSync(
filePath,
`${JSON.stringify(formatTestResults(runResults))}\n`,
);
fs.writeFileSync(filePath, `${jsonString}\n`);
outputStream.write(
`Test results written to: ${path.relative(cwd, filePath)}\n`,
);
} else {
process.stdout.write(
`${JSON.stringify(formatTestResults(runResults))}\n`,
);
process.stdout.write(`${jsonString}\n`);
}
}

Expand Down
Loading