From 5ba0cc9cd3bd865cc3ad08e34e023a1e9aee7d6b Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Tue, 9 Feb 2021 10:31:50 +0100 Subject: [PATCH] fix: print underlying error when global hooks fail (#11003) --- CHANGELOG.md | 1 + e2e/__tests__/globalSetup.test.ts | 41 ++++++++++++++++++++++--- e2e/__tests__/globalTeardown.test.ts | 10 +++--- packages/jest-cli/src/cli/index.ts | 2 +- packages/jest-core/src/runGlobalHook.ts | 35 +++++++++++++++------ 5 files changed, 70 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11beba266b50..ea43cca7622f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - `[jest-circus]` Fixed the issue of beforeAll & afterAll hooks getting executed even if it is inside a skipped `describe` block [#10451](https://github.com/facebook/jest/issues/10451) - `[jest-circus]` Fix `testLocation` on Windows when using `test.each` ([#10871](https://github.com/facebook/jest/pull/10871)) - `[jest-cli]` Use testFailureExitCode when bailing from a failed test ([#10958](https://github.com/facebook/jest/pull/10958)) +- `[jest-cli]` Print custom error if error thrown from global hooks is not an error already ([#11003](https://github.com/facebook/jest/pull/11003)) - `[jest-config]` [**BREAKING**] Change default file extension order by moving json behind ts and tsx ([10572](https://github.com/facebook/jest/pull/10572)) - `[jest-console]` `console.dir` now respects the second argument correctly ([#10638](https://github.com/facebook/jest/pull/10638)) - `[jest-each]` [**BREAKING**] Ignore excess words in headings ([#8766](https://github.com/facebook/jest/pull/8766)) diff --git a/e2e/__tests__/globalSetup.test.ts b/e2e/__tests__/globalSetup.test.ts index a7b993b4ba84..04e08bd4fa0d 100644 --- a/e2e/__tests__/globalSetup.test.ts +++ b/e2e/__tests__/globalSetup.test.ts @@ -8,7 +8,12 @@ import {tmpdir} from 'os'; import * as path from 'path'; import * as fs from 'graceful-fs'; -import {cleanup, runYarnInstall} from '../Utils'; +import { + cleanup, + createEmptyPackage, + runYarnInstall, + writeFiles, +} from '../Utils'; import runJest, {json as runWithJson} from '../runJest'; const DIR = path.join(tmpdir(), 'jest-global-setup'); @@ -19,6 +24,7 @@ const customTransformDIR = path.join( 'jest-global-setup-custom-transform', ); const nodeModulesDIR = path.join(tmpdir(), 'jest-global-setup-node-modules'); +const rejectionDir = path.join(tmpdir(), 'jest-global-setup-rejection'); const e2eDir = path.resolve(__dirname, '../global-setup'); beforeAll(() => { @@ -31,13 +37,16 @@ beforeEach(() => { cleanup(project2DIR); cleanup(customTransformDIR); cleanup(nodeModulesDIR); + cleanup(rejectionDir); }); + afterAll(() => { cleanup(DIR); cleanup(project1DIR); cleanup(project2DIR); cleanup(customTransformDIR); cleanup(nodeModulesDIR); + cleanup(rejectionDir); }); test('globalSetup is triggered once before all test suites', () => { @@ -62,8 +71,9 @@ test('jest throws an error when globalSetup does not export a function', () => { ]); expect(exitCode).toBe(1); - expect(stderr).toMatch( - `TypeError: globalSetup file must export a function at ${setupPath}`, + expect(stderr).toContain('Jest: Got error running globalSetup'); + expect(stderr).toContain( + `globalSetup file must export a function at ${setupPath}`, ); }); @@ -145,8 +155,9 @@ test('globalSetup throws with named export', () => { ]); expect(exitCode).toBe(1); - expect(stderr).toMatch( - `TypeError: globalSetup file must export a function at ${setupPath}`, + expect(stderr).toContain('Jest: Got error running globalSetup'); + expect(stderr).toContain( + `globalSetup file must export a function at ${setupPath}`, ); }); @@ -161,3 +172,23 @@ test('should transform node_modules if configured by transformIgnorePatterns', ( expect(exitCode).toBe(0); }); + +test('properly handle rejections', () => { + createEmptyPackage(rejectionDir, {jest: {globalSetup: '/setup.js'}}); + writeFiles(rejectionDir, { + 'setup.js': ` + module.exports = () => Promise.reject(); + `, + 'test.js': ` + test('dummy', () => { + expect(true).toBe(true); + }); + `, + }); + + const {exitCode, stderr} = runJest(rejectionDir, [`--no-cache`]); + + expect(exitCode).toBe(1); + expect(stderr).toContain('Error: Jest: Got error running globalSetup'); + expect(stderr).toContain('reason: undefined'); +}); diff --git a/e2e/__tests__/globalTeardown.test.ts b/e2e/__tests__/globalTeardown.test.ts index a3947ef1c337..200faff130f4 100644 --- a/e2e/__tests__/globalTeardown.test.ts +++ b/e2e/__tests__/globalTeardown.test.ts @@ -55,8 +55,9 @@ test('jest throws an error when globalTeardown does not export a function', () = ]); expect(exitCode).toBe(1); - expect(stderr).toMatch( - `TypeError: globalTeardown file must export a function at ${teardownPath}`, + expect(stderr).toContain('Jest: Got error running globalTeardown'); + expect(stderr).toContain( + `globalTeardown file must export a function at ${teardownPath}`, ); }); @@ -125,7 +126,8 @@ test('globalTeardown throws with named export', () => { ]); expect(exitCode).toBe(1); - expect(stderr).toMatch( - `TypeError: globalTeardown file must export a function at ${teardownPath}`, + expect(stderr).toContain('Jest: Got error running globalTeardown'); + expect(stderr).toContain( + `globalTeardown file must export a function at ${teardownPath}`, ); }); diff --git a/packages/jest-cli/src/cli/index.ts b/packages/jest-cli/src/cli/index.ts index a20eae54d50d..8290539785d6 100644 --- a/packages/jest-cli/src/cli/index.ts +++ b/packages/jest-cli/src/cli/index.ts @@ -37,7 +37,7 @@ export async function run( } catch (error) { clearLine(process.stderr); clearLine(process.stdout); - if (error.stack) { + if (error?.stack) { console.error(chalk.red(error.stack)); } else { console.error(chalk.red(error)); diff --git a/packages/jest-core/src/runGlobalHook.ts b/packages/jest-core/src/runGlobalHook.ts index c6779becf80e..82dc3a5c1071 100644 --- a/packages/jest-core/src/runGlobalHook.ts +++ b/packages/jest-core/src/runGlobalHook.ts @@ -5,11 +5,13 @@ * LICENSE file in the root directory of this source tree. */ +import * as util from 'util'; import pEachSeries = require('p-each-series'); import {ScriptTransformer} from '@jest/transform'; import type {Config} from '@jest/types'; import type {Test} from 'jest-runner'; import {interopRequireDefault} from 'jest-util'; +import prettyFormat from 'pretty-format'; export default async ({ allTests, @@ -29,7 +31,7 @@ export default async ({ } if (globalModulePaths.size > 0) { - await pEachSeries(Array.from(globalModulePaths), async modulePath => { + await pEachSeries(globalModulePaths, async modulePath => { if (!modulePath) { return; } @@ -45,17 +47,32 @@ export default async ({ const transformer = new ScriptTransformer(projectConfig); - await transformer.requireAndTranspileModule(modulePath, async m => { - const globalModule = interopRequireDefault(m).default; + try { + await transformer.requireAndTranspileModule(modulePath, async m => { + const globalModule = interopRequireDefault(m).default; - if (typeof globalModule !== 'function') { - throw new TypeError( - `${moduleName} file must export a function at ${modulePath}`, - ); + if (typeof globalModule !== 'function') { + throw new TypeError( + `${moduleName} file must export a function at ${modulePath}`, + ); + } + + await globalModule(globalConfig); + }); + } catch (error) { + if (util.types.isNativeError(error)) { + error.message = `Jest: Got error running ${moduleName} - ${modulePath}, reason: ${error.message}`; + + throw error; } - await globalModule(globalConfig); - }); + throw new Error( + `Jest: Got error running ${moduleName} - ${modulePath}, reason: ${prettyFormat( + error, + {maxDepth: 3}, + )}`, + ); + } }); }