From 48e37b98648f911fee9f6aef1c702663221e2324 Mon Sep 17 00:00:00 2001 From: Olivier Beaulieu Date: Wed, 30 Jan 2019 01:55:05 -0500 Subject: [PATCH] Better error messages when the jest environment is used after teardown by async code (#7756) --- CHANGELOG.md | 1 + .../environmentAfterTeardown.test.js.snap | 13 +++++ .../environmentAfterTeardown.test.js | 24 ++++++++ .../__tests__/afterTeardown.test.js | 13 +++++ e2e/environment-after-teardown/package.json | 5 ++ packages/jest-runtime/src/index.js | 56 ++++++++++++------- 6 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 e2e/__tests__/__snapshots__/environmentAfterTeardown.test.js.snap create mode 100644 e2e/__tests__/environmentAfterTeardown.test.js create mode 100644 e2e/environment-after-teardown/__tests__/afterTeardown.test.js create mode 100644 e2e/environment-after-teardown/package.json diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf5d5c30136..3e96b703a06a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - `[jest-resolve]`: Pass default resolver into custom resolvers ([#7714](https://github.com/facebook/jest/pull/7714)) - `[jest-cli]`: `global{Setup,Teardown}` use default export with es modules ([#7750](https://github.com/facebook/jest/pull/7750)) +- `[jest-runtime]` Better error messages when the jest environment is used after teardown by async code ([#7756](https://github.com/facebook/jest/pull/7756)) ### Fixes diff --git a/e2e/__tests__/__snapshots__/environmentAfterTeardown.test.js.snap b/e2e/__tests__/__snapshots__/environmentAfterTeardown.test.js.snap new file mode 100644 index 000000000000..8364b2e50d2a --- /dev/null +++ b/e2e/__tests__/__snapshots__/environmentAfterTeardown.test.js.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`prints useful error for environment methods after test is done 1`] = ` +ReferenceError: You are trying to access a property or method of the Jest environment after it has been torn down. + + 9 | test('access environment methods after done', () => { + 10 | setTimeout(() => { + > 11 | jest.clearAllTimers(); + | ^ + 12 | }, 0); + 13 | }); + 14 | +`; diff --git a/e2e/__tests__/environmentAfterTeardown.test.js b/e2e/__tests__/environmentAfterTeardown.test.js new file mode 100644 index 000000000000..084c7787887f --- /dev/null +++ b/e2e/__tests__/environmentAfterTeardown.test.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import runJest from '../runJest'; +import {wrap} from 'jest-snapshot-serializer-raw'; + +test('prints useful error for environment methods after test is done', () => { + const {stderr} = runJest('environment-after-teardown'); + const interestingLines = stderr + .split('\n') + .slice(9, 18) + .join('\n'); + + expect(wrap(interestingLines)).toMatchSnapshot(); + expect(stderr.split('\n')[9]).toBe( + 'ReferenceError: You are trying to access a property or method of the Jest environment after it has been torn down.', + ); +}); diff --git a/e2e/environment-after-teardown/__tests__/afterTeardown.test.js b/e2e/environment-after-teardown/__tests__/afterTeardown.test.js new file mode 100644 index 000000000000..f20365af5970 --- /dev/null +++ b/e2e/environment-after-teardown/__tests__/afterTeardown.test.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +test('access environment methods after done', () => { + setTimeout(() => { + jest.clearAllTimers(); + }, 0); +}); diff --git a/e2e/environment-after-teardown/package.json b/e2e/environment-after-teardown/package.json new file mode 100644 index 000000000000..148788b25446 --- /dev/null +++ b/e2e/environment-after-teardown/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "testEnvironment": "node" + } +} diff --git a/packages/jest-runtime/src/index.js b/packages/jest-runtime/src/index.js index 7c69e5925152..91d6e6a19927 100644 --- a/packages/jest-runtime/src/index.js +++ b/packages/jest-runtime/src/index.js @@ -684,19 +684,8 @@ class Runtime { const runScript = this._environment.runScript(transformedFile.script); if (runScript === null) { - const originalStack = new ReferenceError( + this._logFormattedReferenceError( 'You are trying to `import` a file after the Jest environment has been torn down.', - ).stack - .split('\n') - // Remove this file from the stack (jest-message-utils will keep one line) - .filter(line => line.indexOf(__filename) === -1) - .join('\n'); - - const {message, stack} = separateMessageFromStack(originalStack); - - console.error( - `\n${message}\n` + - formatStackTrace(stack, this._config, {noStackTrace: false}), ); process.exitCode = 1; return; @@ -978,15 +967,26 @@ class Runtime { return jestObject; }; + const _getFakeTimers = () => { + if (!this._environment.fakeTimers) { + this._logFormattedReferenceError( + 'You are trying to access a property or method of the Jest environment after it has been torn down.', + ); + process.exitCode = 1; + } + + return this._environment.fakeTimers; + }; + const jestObject = { addMatchers: (matchers: Object) => this._environment.global.jasmine.addMatchers(matchers), advanceTimersByTime: (msToRun: number) => - this._environment.fakeTimers.advanceTimersByTime(msToRun), + _getFakeTimers().advanceTimersByTime(msToRun), autoMockOff: disableAutomock, autoMockOn: enableAutomock, clearAllMocks, - clearAllTimers: () => this._environment.fakeTimers.clearAllTimers(), + clearAllTimers: () => _getFakeTimers().clearAllTimers(), deepUnmock, disableAutomock, doMock: mock, @@ -995,7 +995,7 @@ class Runtime { fn, genMockFromModule: (moduleName: string) => this._generateMock(from, moduleName), - getTimerCount: () => this._environment.fakeTimers.getTimerCount(), + getTimerCount: () => _getFakeTimers().getTimerCount(), isMockFunction: this._moduleMocker.isMockFunction, isolateModules, mock, @@ -1006,13 +1006,12 @@ class Runtime { resetModules, restoreAllMocks, retryTimes, - runAllImmediates: () => this._environment.fakeTimers.runAllImmediates(), - runAllTicks: () => this._environment.fakeTimers.runAllTicks(), - runAllTimers: () => this._environment.fakeTimers.runAllTimers(), - runOnlyPendingTimers: () => - this._environment.fakeTimers.runOnlyPendingTimers(), + runAllImmediates: () => _getFakeTimers().runAllImmediates(), + runAllTicks: () => _getFakeTimers().runAllTicks(), + runAllTimers: () => _getFakeTimers().runAllTimers(), + runOnlyPendingTimers: () => _getFakeTimers().runOnlyPendingTimers(), runTimersToTime: (msToRun: number) => - this._environment.fakeTimers.advanceTimersByTime(msToRun), + _getFakeTimers().advanceTimersByTime(msToRun), setMock: (moduleName: string, mock: Object) => setMockFactory(moduleName, () => mock), setTimeout, @@ -1023,6 +1022,21 @@ class Runtime { }; return jestObject; } + + _logFormattedReferenceError(errorMessage: string) { + const originalStack = new ReferenceError(errorMessage).stack + .split('\n') + // Remove this file from the stack (jest-message-utils will keep one line) + .filter(line => line.indexOf(__filename) === -1) + .join('\n'); + + const {message, stack} = separateMessageFromStack(originalStack); + + console.error( + `\n${message}\n` + + formatStackTrace(stack, this._config, {noStackTrace: false}), + ); + } } Runtime.ScriptTransformer = ScriptTransformer;