From 0dac3cea0a9e9a90047ae3f6cc117c681f24563d Mon Sep 17 00:00:00 2001 From: Zero Cho Date: Tue, 29 Nov 2016 10:42:23 -0800 Subject: [PATCH] Always recreate mocks in FakeTimers.useFakeTimers() (#2109) * Always recreate mocks in FakeTimers.useFakeTimers() If the flag resetMocks or jest.resetAllMocks is used in the test, all the timer tests following that will fail with no way of recovering. jest.resetAllMocks resets everything, including the one created by fakeTimers. There is also no public methods that we can use to reinstantiate a new FakeTimer object to mitigate that. Given both mock and fake timers are part of the jest, they should be working together out-of-the box without extra code gluing them. We change the useFakeTimers method to always create new mocks so it would not be reverted. * Cleanup FakeTimers handling of `process.nextTick`. --- .../timer_after_resetAllMocks-test.js | 20 ++++++ .../timer_after_resetAllMocks/index.js | 11 +++ .../timer_after_resetAllMocks/package.json | 6 ++ .../timer_and_mock.test.js | 18 +++++ packages/jest-util/src/FakeTimers.js | 71 +++++++------------ .../src/__tests__/FakeTimers-test.js | 58 ++++++++------- 6 files changed, 115 insertions(+), 69 deletions(-) create mode 100644 integration_tests/__tests__/timer_after_resetAllMocks-test.js create mode 100644 integration_tests/timer_after_resetAllMocks/index.js create mode 100644 integration_tests/timer_after_resetAllMocks/package.json create mode 100644 integration_tests/timer_after_resetAllMocks/timer_and_mock.test.js diff --git a/integration_tests/__tests__/timer_after_resetAllMocks-test.js b/integration_tests/__tests__/timer_after_resetAllMocks-test.js new file mode 100644 index 000000000000..907355de2f3f --- /dev/null +++ b/integration_tests/__tests__/timer_after_resetAllMocks-test.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails oncall+jsinfra + */ +'use strict'; + +const runJest = require('../runJest'); +const skipOnWindows = require('skipOnWindows'); + +skipOnWindows.suite(); + +test('run timers after resetAllMocks test', () => { + const result = runJest('timer_after_resetAllMocks'); + expect(result.status).toBe(0); +}); diff --git a/integration_tests/timer_after_resetAllMocks/index.js b/integration_tests/timer_after_resetAllMocks/index.js new file mode 100644 index 000000000000..065ab53aaf25 --- /dev/null +++ b/integration_tests/timer_after_resetAllMocks/index.js @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +module.exports = () => {}; diff --git a/integration_tests/timer_after_resetAllMocks/package.json b/integration_tests/timer_after_resetAllMocks/package.json new file mode 100644 index 000000000000..24b23cafe0e3 --- /dev/null +++ b/integration_tests/timer_after_resetAllMocks/package.json @@ -0,0 +1,6 @@ +{ + "jest": { + "testEnvironment": "node", + "resetMocks": false + } +} diff --git a/integration_tests/timer_after_resetAllMocks/timer_and_mock.test.js b/integration_tests/timer_after_resetAllMocks/timer_and_mock.test.js new file mode 100644 index 000000000000..969cd00ede8d --- /dev/null +++ b/integration_tests/timer_after_resetAllMocks/timer_and_mock.test.js @@ -0,0 +1,18 @@ +describe('timers', () => { + it('should work before calling resetAllMocks', () => { + jest.useFakeTimers(); + const f = jest.fn(); + setImmediate(() => f()); + jest.runAllImmediates(); + expect(f.mock.calls.length).toBe(1); + }); + + it('should not break after calling resetAllMocks', () => { + jest.resetAllMocks(); + jest.useFakeTimers(); + const f = jest.fn(); + setImmediate(() => f()); + jest.runAllImmediates(); + expect(f.mock.calls.length).toBe(1); + }); +}); diff --git a/packages/jest-util/src/FakeTimers.js b/packages/jest-util/src/FakeTimers.js index ffb0e4f425c4..969097d0aa1f 100644 --- a/packages/jest-util/src/FakeTimers.js +++ b/packages/jest-util/src/FakeTimers.js @@ -35,7 +35,7 @@ type TimerAPI = { clearImmediate(timeoutId?: any): void, clearInterval(intervalId?: number): void, clearTimeout(timeoutId?: any): void, - nextTick?: (callback: Callback) => void, + nextTick: (callback: Callback) => void, setImmediate(callback: any, ms?: number, ...args: Array): number, setInterval(callback: any, ms?: number, ...args: Array): number, setTimeout(callback: any, ms?: number, ...args: Array): number, @@ -44,7 +44,6 @@ type TimerAPI = { const MS_IN_A_YEAR = 31536000000; class FakeTimers { - _cancelledImmediates: {[key: TimerID]: boolean}; _cancelledTicks: {[key: TimerID]: boolean}; _config: Config; @@ -53,6 +52,7 @@ class FakeTimers { _global: Global; _immediates: Array; _maxLoops: number; + _moduleMocker: ModuleMocker; _now: number; _ticks: Array; _timerAPIs: TimerAPI; @@ -70,37 +70,21 @@ class FakeTimers { this._config = config; this._maxLoops = maxLoops || 100000; this._uuidCounter = 1; - - this.reset(); + this._moduleMocker = moduleMocker; // Store original timer APIs for future reference this._timerAPIs = { clearImmediate: global.clearImmediate, clearInterval: global.clearInterval, clearTimeout: global.clearTimeout, + nextTick: global.process && global.process.nextTick, setImmediate: global.setImmediate, setInterval: global.setInterval, setTimeout: global.setTimeout, }; - const fn = impl => moduleMocker.getMockFn().mockImpl(impl); - - this._fakeTimerAPIs = { - clearImmediate: fn(this._fakeClearImmediate.bind(this)), - clearInterval: fn(this._fakeClearTimer.bind(this)), - clearTimeout: fn(this._fakeClearTimer.bind(this)), - setImmediate: fn(this._fakeSetImmediate.bind(this)), - setInterval: fn(this._fakeSetInterval.bind(this)), - setTimeout: fn(this._fakeSetTimeout.bind(this)), - }; - - // If there's a process.nextTick on the global, mock it out - // (only applicable to node/node-emulating environments) - if (typeof global.process === 'object' - && typeof global.process.nextTick === 'function') { - this._timerAPIs.nextTick = global.process.nextTick; - this._fakeTimerAPIs.nextTick = fn(this._fakeNextTick.bind(this)); - } + this.reset(); + this._createMocks(); // These globally-accessible function are now deprecated! // They will go away very soon, so do not use them! @@ -270,19 +254,14 @@ class FakeTimers { } runWithRealTimers(cb: Callback) { - const hasNextTick = - typeof this._global.process === 'object' - && typeof this._global.process.nextTick === 'function'; - const prevClearImmediate = this._global.clearImmediate; const prevClearInterval = this._global.clearInterval; const prevClearTimeout = this._global.clearTimeout; + const prevNextTick = this._global.process.nextTick; const prevSetImmediate = this._global.setImmediate; const prevSetInterval = this._global.setInterval; const prevSetTimeout = this._global.setTimeout; - const prevNextTick = hasNextTick ? this._global.process.nextTick : null; - this.useRealTimers(); let cbErr = null; @@ -297,12 +276,10 @@ class FakeTimers { this._global.clearImmediate = prevClearImmediate; this._global.clearInterval = prevClearInterval; this._global.clearTimeout = prevClearTimeout; + this._global.process.nextTick = prevNextTick; this._global.setImmediate = prevSetImmediate; this._global.setInterval = prevSetInterval; this._global.setTimeout = prevSetTimeout; - if (hasNextTick) { - this._global.process.nextTick = prevNextTick; - } if (errThrown) { throw cbErr; @@ -310,35 +287,25 @@ class FakeTimers { } useRealTimers() { - const hasNextTick = - typeof this._global.process === 'object' - && typeof this._global.process.nextTick === 'function'; - this._global.clearImmediate = this._timerAPIs.clearImmediate; this._global.clearInterval = this._timerAPIs.clearInterval; this._global.clearTimeout = this._timerAPIs.clearTimeout; + this._global.process.nextTick = this._timerAPIs.nextTick; this._global.setImmediate = this._timerAPIs.setImmediate; this._global.setInterval = this._timerAPIs.setInterval; this._global.setTimeout = this._timerAPIs.setTimeout; - if (hasNextTick) { - this._global.process.nextTick = this._timerAPIs.nextTick; - } } useFakeTimers() { - const hasNextTick = - typeof this._global.process === 'object' - && typeof this._global.process.nextTick === 'function'; + this._createMocks(); this._global.clearImmediate = this._fakeTimerAPIs.clearImmediate; this._global.clearInterval = this._fakeTimerAPIs.clearInterval; this._global.clearTimeout = this._fakeTimerAPIs.clearTimeout; + this._global.process.nextTick = this._fakeTimerAPIs.nextTick; this._global.setImmediate = this._fakeTimerAPIs.setImmediate; this._global.setInterval = this._fakeTimerAPIs.setInterval; this._global.setTimeout = this._fakeTimerAPIs.setTimeout; - if (hasNextTick) { - this._global.process.nextTick = this._fakeTimerAPIs.nextTick; - } } _checkFakeTimers() { @@ -355,6 +322,20 @@ class FakeTimers { } } + _createMocks() { + const fn = impl => this._moduleMocker.getMockFn().mockImpl(impl); + + this._fakeTimerAPIs = { + clearImmediate: fn(this._fakeClearImmediate.bind(this)), + clearInterval: fn(this._fakeClearTimer.bind(this)), + clearTimeout: fn(this._fakeClearTimer.bind(this)), + nextTick: fn(this._fakeNextTick.bind(this)), + setImmediate: fn(this._fakeSetImmediate.bind(this)), + setInterval: fn(this._fakeSetInterval.bind(this)), + setTimeout: fn(this._fakeSetTimeout.bind(this)), + }; + } + _fakeClearTimer(uuid: TimerID) { if (this._timers.hasOwnProperty(uuid)) { delete this._timers[uuid]; @@ -383,7 +364,7 @@ class FakeTimers { }); const cancelledTicks = this._cancelledTicks; - this._timerAPIs.nextTick && this._timerAPIs.nextTick(() => { + this._timerAPIs.nextTick(() => { if (this._blocked) {return;} if (!cancelledTicks.hasOwnProperty(uuid)) { // Callback may throw, so update the map prior calling. diff --git a/packages/jest-util/src/__tests__/FakeTimers-test.js b/packages/jest-util/src/__tests__/FakeTimers-test.js index 11758c882144..ec8db4c9a6f5 100644 --- a/packages/jest-util/src/__tests__/FakeTimers-test.js +++ b/packages/jest-util/src/__tests__/FakeTimers-test.js @@ -25,34 +25,34 @@ describe('FakeTimers', () => { describe('construction', () => { /* eslint-disable no-new */ it('installs setTimeout mock', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); expect(global.setTimeout).not.toBe(undefined); }); it('installs clearTimeout mock', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); expect(global.clearTimeout).not.toBe(undefined); }); it('installs setInterval mock', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); expect(global.setInterval).not.toBe(undefined); }); it('installs clearInterval mock', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); expect(global.clearInterval).not.toBe(undefined); }); - it('mocks process.nextTick if on exists on global', () => { + it('mocks process.nextTick if it exists on global', () => { const origNextTick = () => {}; const global = { process: { @@ -64,16 +64,10 @@ describe('FakeTimers', () => { expect(global.process.nextTick).not.toBe(origNextTick); }); - it('doesn\'t mock process.nextTick if real impl isnt present', () => { - const global = {}; - const timers = new FakeTimers(global, moduleMocker); - timers.useFakeTimers(); - expect(global.process).toBe(undefined); - }); - it('mocks setImmediate if it exists on global', () => { const origSetImmediate = () => {}; const global = { + process, setImmediate: origSetImmediate, }; const timers = new FakeTimers(global, moduleMocker); @@ -86,6 +80,7 @@ describe('FakeTimers', () => { const origClearImmediate = () => {}; const global = { clearImmediate: origClearImmediate, + process, setImmediate: origSetImmediate, }; const timers = new FakeTimers(global, moduleMocker); @@ -190,6 +185,7 @@ describe('FakeTimers', () => { const nativeSetImmediate = jest.genMockFn(); const global = { + process, setImmediate: nativeSetImmediate, }; @@ -235,6 +231,7 @@ describe('FakeTimers', () => { const nativeSetImmediate = jest.genMockFn(); const global = { + process, setImmediate: nativeSetImmediate, }; @@ -257,6 +254,7 @@ describe('FakeTimers', () => { const nativeSetImmediate = jest.genMockFn(); const global = { + process, setImmediate: nativeSetImmediate, }; @@ -301,7 +299,7 @@ describe('FakeTimers', () => { describe('runAllTimers', () => { it('runs all timers in order', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); @@ -347,6 +345,7 @@ describe('FakeTimers', () => { it('does nothing when no timers have been scheduled', () => { const nativeSetTimeout = jest.genMockFn(); const global = { + process, setTimeout: nativeSetTimeout, }; @@ -356,7 +355,7 @@ describe('FakeTimers', () => { }); it('only runs a setTimeout callback once (ever)', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); @@ -372,7 +371,7 @@ describe('FakeTimers', () => { }); it('runs callbacks with arguments after the interval', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); @@ -389,6 +388,7 @@ describe('FakeTimers', () => { const nativeSetTimeout = jest.genMockFn(); const global = { + process, setTimeout: nativeSetTimeout, }; @@ -404,7 +404,7 @@ describe('FakeTimers', () => { }); it('throws before allowing infinite recursion', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker, null, 100); timers.useFakeTimers(); @@ -423,7 +423,7 @@ describe('FakeTimers', () => { describe('runTimersToTime', () => { it('runs timers in order', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); @@ -470,7 +470,7 @@ describe('FakeTimers', () => { }); it('does nothing when no timers have been scheduled', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); @@ -478,7 +478,7 @@ describe('FakeTimers', () => { }); it('throws before allowing infinite recursion', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker, null, 100); timers.useFakeTimers(); @@ -497,7 +497,7 @@ describe('FakeTimers', () => { describe('reset', () => { it('resets all pending setTimeouts', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); @@ -510,7 +510,7 @@ describe('FakeTimers', () => { }); it('resets all pending setIntervals', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); @@ -543,7 +543,7 @@ describe('FakeTimers', () => { }); it('resets current runTimersToTime time cursor', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); @@ -564,6 +564,7 @@ describe('FakeTimers', () => { const nativeSetImmediate = jest.genMockFn(); const global = { + process, setImmediate: nativeSetImmediate, }; @@ -612,7 +613,7 @@ describe('FakeTimers', () => { }); it('does not run timers that were cleared in another timer', () => { - const global = {}; + const global = {process}; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); @@ -637,6 +638,7 @@ describe('FakeTimers', () => { const global = { clearInterval: nativeClearInterval, clearTimeout: nativeClearTimeout, + process, setInterval: nativeSetInterval, setTimeout: nativeSetTimeout, }; @@ -682,6 +684,7 @@ describe('FakeTimers', () => { const global = { clearInterval: nativeClearInterval, clearTimeout: nativeClearTimeout, + process, setInterval: nativeSetInterval, setTimeout: nativeSetTimeout, }; @@ -735,7 +738,10 @@ describe('FakeTimers', () => { it('resets mock timer functions even if callback throws', () => { const nativeSetTimeout = jest.genMockFn(); - const global = {setTimeout: nativeSetTimeout}; + const global = { + process, + setTimeout: nativeSetTimeout, + }; const timers = new FakeTimers(global, moduleMocker); timers.useFakeTimers(); @@ -764,6 +770,7 @@ describe('FakeTimers', () => { const global = { clearInterval: nativeClearInterval, clearTimeout: nativeClearTimeout, + process, setInterval: nativeSetInterval, setTimeout: nativeSetTimeout, }; @@ -809,6 +816,7 @@ describe('FakeTimers', () => { const global = { clearImmediate: nativeClearImmediate, + process, setImmediate: nativeSetImmediate, }; const timers = new FakeTimers(global, moduleMocker); @@ -836,6 +844,7 @@ describe('FakeTimers', () => { const global = { clearInterval: nativeClearInterval, clearTimeout: nativeClearTimeout, + process, setInterval: nativeSetInterval, setTimeout: nativeSetTimeout, }; @@ -881,6 +890,7 @@ describe('FakeTimers', () => { const global = { clearImmediate: nativeClearImmediate, + process, setImmediate: nativeSetImmediate, }; const fakeTimers = new FakeTimers(global, moduleMocker);