Skip to content

Commit

Permalink
Always recreate mocks in FakeTimers.useFakeTimers() (jestjs#2109)
Browse files Browse the repository at this point in the history
* 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`.
  • Loading branch information
itszero authored and cpojer committed Nov 29, 2016
1 parent dadd8e0 commit 0dac3ce
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 69 deletions.
20 changes: 20 additions & 0 deletions integration_tests/__tests__/timer_after_resetAllMocks-test.js
Original file line number Diff line number Diff line change
@@ -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);
});
11 changes: 11 additions & 0 deletions integration_tests/timer_after_resetAllMocks/index.js
Original file line number Diff line number Diff line change
@@ -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 = () => {};
6 changes: 6 additions & 0 deletions integration_tests/timer_after_resetAllMocks/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"jest": {
"testEnvironment": "node",
"resetMocks": false
}
}
18 changes: 18 additions & 0 deletions integration_tests/timer_after_resetAllMocks/timer_and_mock.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
71 changes: 26 additions & 45 deletions packages/jest-util/src/FakeTimers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>): number,
setInterval(callback: any, ms?: number, ...args: Array<any>): number,
setTimeout(callback: any, ms?: number, ...args: Array<any>): number,
Expand All @@ -44,7 +44,6 @@ type TimerAPI = {
const MS_IN_A_YEAR = 31536000000;

class FakeTimers {

_cancelledImmediates: {[key: TimerID]: boolean};
_cancelledTicks: {[key: TimerID]: boolean};
_config: Config;
Expand All @@ -53,6 +52,7 @@ class FakeTimers {
_global: Global;
_immediates: Array<Tick>;
_maxLoops: number;
_moduleMocker: ModuleMocker;
_now: number;
_ticks: Array<Tick>;
_timerAPIs: TimerAPI;
Expand All @@ -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!
Expand Down Expand Up @@ -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;
Expand All @@ -297,48 +276,36 @@ 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;
}
}

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() {
Expand All @@ -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];
Expand Down Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 0dac3ce

Please sign in to comment.