Skip to content

Commit

Permalink
Always recreate mocks in FakeTimers.useFakeTimers()
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Zero Cho committed Nov 16, 2016
1 parent 3291cc2 commit 886718c
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 14 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);
});
});
40 changes: 26 additions & 14 deletions packages/jest-util/src/FakeTimers.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class FakeTimers {
_global: Global;
_immediates: Array<Tick>;
_maxLoops: number;
_moduleMocker: ModuleMocker;
_now: number;
_ticks: Array<Tick>;
_timerAPIs: TimerAPI;
Expand All @@ -70,8 +71,7 @@ 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 = {
Expand All @@ -83,25 +83,16 @@ class FakeTimers {
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!
// Instead, use the versions available on the `jest` object
Expand Down Expand Up @@ -330,6 +321,8 @@ class FakeTimers {
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;
Expand All @@ -355,6 +348,25 @@ 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)),
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 (this._timerAPIs.nextTick) {
this._fakeTimerAPIs.nextTick = fn(this._fakeNextTick.bind(this));
}
}

_fakeClearTimer(uuid: TimerID) {
if (this._timers.hasOwnProperty(uuid)) {
delete this._timers[uuid];
Expand Down

0 comments on commit 886718c

Please sign in to comment.