Skip to content

Commit

Permalink
feat(jest-util): use lolex as implementation for fake timers
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Feb 6, 2019
1 parent bc0f21d commit a9bcc97
Show file tree
Hide file tree
Showing 26 changed files with 282 additions and 927 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -20,6 +20,7 @@
- `[jest-jasmine2]` Will now only execute at most 5 concurrent tests _within the same testsuite_ when using `test.concurrent` ([#7770](https://github.com/facebook/jest/pull/7770))
- `[jest-circus]` Same as `[jest-jasmine2]`, only 5 tests will run concurrently by default ([#7770](https://github.com/facebook/jest/pull/7770))
- `[jest-config]` A new `maxConcurrency` option allows to change the number of tests allowed to run concurrently ([#7770](https://github.com/facebook/jest/pull/7770))
- `[jest-util]`[**BREAKING**] Replace Jest's fake timers implementation with Lolex ([#5171](https://github.com/facebook/jest/pull/5171))

### Fixes

Expand Down
12 changes: 8 additions & 4 deletions docs/JestObjectAPI.md
Expand Up @@ -434,10 +434,6 @@ When this API is called, all pending "macro-tasks" that have been queued via `se

This is often useful for synchronously executing setTimeouts during a test in order to synchronously assert about some behavior that would only happen after the `setTimeout()` or `setInterval()` callbacks executed. See the [Timer mocks](TimerMocks.md) doc for more information.

### `jest.runAllImmediates()`

Exhausts all tasks queued by `setImmediate()`.

### `jest.advanceTimersByTime(msToRun)`

##### renamed in Jest **22.0.0+**
Expand All @@ -464,6 +460,14 @@ This means, if any timers have been scheduled (but have not yet executed), they

Returns the number of fake timers still left to run.

### `.jest.setSystemTime()`

Set the current system time used by fake timers. Simulates a user changing the system clock while your program is running. It affects the current time but it does not in itself cause e.g. timers to fire; they will fire exactly as they would have done without the call to `jest.setSystemTime()`.

### `.jest.getRealSystemTime()`

When mocking time, `Date.now()` will also be mocked. If you for some reason need access to the real current time, you can invoke this function.

## Misc

### `jest.setTimeout(timeout)`
Expand Down
9 changes: 2 additions & 7 deletions e2e/__tests__/fakePromises.test.js
Expand Up @@ -10,13 +10,8 @@
import runJest from '../runJest';

describe('Fake promises', () => {
it('should be possible to resolve with fake timers using immediates', () => {
const result = runJest('fake-promises/immediate');
expect(result.status).toBe(0);
});

it('should be possible to resolve with fake timers using asap', () => {
const result = runJest('fake-promises/asap');
it('should be possible to resolve with fake timers', () => {
const result = runJest('fake-promises');
expect(result.status).toBe(0);
});
});
File renamed without changes.
19 changes: 0 additions & 19 deletions e2e/fake-promises/immediate/__tests__/generator.test.js

This file was deleted.

10 changes: 0 additions & 10 deletions e2e/fake-promises/immediate/fake-promises.js

This file was deleted.

9 changes: 0 additions & 9 deletions e2e/fake-promises/immediate/package.json

This file was deleted.

File renamed without changes.
12 changes: 6 additions & 6 deletions e2e/timer-reset-mocks/after-reset-all-mocks/timerAndMock.test.js
Expand Up @@ -4,17 +4,17 @@ 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);
setTimeout(f, 0);
jest.runAllTimers();
expect(f).toHaveBeenCalledTimes(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);
setTimeout(f, 0);
jest.runAllTimers();
expect(f).toHaveBeenCalledTimes(1);
});
});
6 changes: 3 additions & 3 deletions e2e/timer-reset-mocks/with-reset-mocks/timerWithMock.test.js
Expand Up @@ -4,8 +4,8 @@ describe('timers', () => {
it('should work before calling resetAllMocks', () => {
const f = jest.fn();
jest.useFakeTimers();
setImmediate(() => f());
jest.runAllImmediates();
expect(f.mock.calls.length).toBe(1);
setTimeout(f, 0);
jest.runAllTimers();
expect(f).toHaveBeenCalledTimes(1);
});
});
9 changes: 5 additions & 4 deletions examples/timer/__tests__/infinite_timer_game.test.js
Expand Up @@ -5,15 +5,16 @@
jest.useFakeTimers();

it('schedules a 10-second timer after 1 second', () => {
jest.spyOn(global, 'setTimeout');
const infiniteTimerGame = require('../infiniteTimerGame');
const callback = jest.fn();

infiniteTimerGame(callback);

// At this point in time, there should have been a single call to
// setTimeout to schedule the end of the game in 1 second.
expect(setTimeout.mock.calls.length).toBe(1);
expect(setTimeout.mock.calls[0][1]).toBe(1000);
expect(setTimeout).toBeCalledTimes(1);
expect(setTimeout).toHaveBeenNthCalledWith(1, expect.any(Function), 1000);

// Fast forward and exhaust only currently pending timers
// (but not any new timers that get created during that process)
Expand All @@ -24,6 +25,6 @@ it('schedules a 10-second timer after 1 second', () => {

// And it should have created a new timer to start the game over in
// 10 seconds
expect(setTimeout.mock.calls.length).toBe(2);
expect(setTimeout.mock.calls[1][1]).toBe(10000);
expect(setTimeout).toBeCalledTimes(2);
expect(setTimeout).toHaveBeenNthCalledWith(2, expect.any(Function), 10000);
});
11 changes: 7 additions & 4 deletions examples/timer/__tests__/timer_game.test.js
Expand Up @@ -5,12 +5,15 @@
jest.useFakeTimers();

describe('timerGame', () => {
beforeEach(() => {
jest.spyOn(global, 'setTimeout');
});
it('waits 1 second before ending the game', () => {
const timerGame = require('../timerGame');
timerGame();

expect(setTimeout.mock.calls.length).toBe(1);
expect(setTimeout.mock.calls[0][1]).toBe(1000);
expect(setTimeout).toBeCalledTimes(1);
expect(setTimeout).toBeCalledWith(expect.any(Function), 1000);
});

it('calls the callback after 1 second via runAllTimers', () => {
Expand All @@ -27,7 +30,7 @@ describe('timerGame', () => {

// Now our callback should have been called!
expect(callback).toBeCalled();
expect(callback.mock.calls.length).toBe(1);
expect(callback).toBeCalledTimes(1);
});

it('calls the callback after 1 second via advanceTimersByTime', () => {
Expand All @@ -44,6 +47,6 @@ describe('timerGame', () => {

// Now our callback should have been called!
expect(callback).toBeCalled();
expect(callback.mock.calls.length).toBe(1);
expect(callback).toBeCalledTimes(1);
});
});
8 changes: 4 additions & 4 deletions flow-typed/npm/jest_v23.x.x.js
Expand Up @@ -818,10 +818,6 @@ type JestObjectType = {
* setInterval(), and setImmediate()).
*/
runAllTimers(): void,
/**
* Exhausts all tasks queued by setImmediate().
*/
runAllImmediates(): void,
/**
* Executes only the macro task queue (i.e. all tasks queued by setTimeout()
* or setInterval() and setImmediate()).
Expand Down Expand Up @@ -862,6 +858,10 @@ type JestObjectType = {
* Instructs Jest to use the real versions of the standard timer functions.
*/
useRealTimers(): JestObjectType,
/**
* Get the remaining number of fake timers scheduled
*/
getTimerCount(): number,
/**
* Creates a mock function similar to jest.fn but also tracks calls to
* object[methodName].
Expand Down
50 changes: 50 additions & 0 deletions flow-typed/npm/lolex_v2.x.x.js
@@ -0,0 +1,50 @@
// flow-typed signature: a865cf1b7ee719c2a40b85dc8dccf56c
// flow-typed version: fc3f3a2e99/lolex_v2.x.x/flow_>=v0.64.x

// @flow
declare module 'lolex' {
declare opaque type ImmediateID;
declare type installConfig = {
target?: Object,
now?: number | Date,
toFake?: string[],
loopLimit?: number,
shouldAdvanceTime?: boolean,
advanceTimeDelta?: number,
};
declare type lolex = {
createClock(now?: number, loopLimit?: number): Clock,
install(config?: installConfig): Clock,
timers: Object,
withGlobal(global: Object): lolex,
};
declare type Clock = {
setTimeout: typeof setTimeout;
clearTimeout: typeof clearTimeout;
setInterval: typeof setInterval;
clearInterval: typeof clearInterval;
setImmediate: typeof setImmediate;
clearImmediate: typeof clearImmediate;
requestAnimationFrame: typeof requestAnimationFrame;
cancelAnimationFrame: typeof cancelAnimationFrame;
hrtime: typeof process.hrtime;
nextTick: typeof process.nextTick;
now: number;
performance?: {
now: typeof performance.now,
};
tick(time: number | string): void;
next(): void;
reset(): void;
runAll(): void;
runMicrotasks(): void;
runToFrame(): void;
runToLast(): void;
setSystemTime(now?: number | Date): void;
uninstall(): Object[];
Date: typeof Date;
Performance: typeof Performance;
countTimers(): number;
}
declare module.exports: lolex;
}
9 changes: 1 addition & 8 deletions packages/jest-environment-jsdom/src/index.js
Expand Up @@ -18,7 +18,7 @@ import {JSDOM, VirtualConsole} from 'jsdom';

class JSDOMEnvironment {
dom: ?Object;
fakeTimers: ?FakeTimers<number>;
fakeTimers: ?FakeTimers;
global: ?Global;
errorEventListener: ?Function;
moduleMocker: ?ModuleMocker;
Expand Down Expand Up @@ -65,16 +65,9 @@ class JSDOMEnvironment {

this.moduleMocker = new mock.ModuleMocker(global);

const timerConfig = {
idToRef: (id: number) => id,
refToId: (ref: number) => ref,
};

this.fakeTimers = new FakeTimers({
config,
global,
moduleMocker: this.moduleMocker,
timerConfig,
});
}

Expand Down
21 changes: 1 addition & 20 deletions packages/jest-environment-node/src/index.js
Expand Up @@ -24,7 +24,7 @@ type Timer = {|

class NodeEnvironment {
context: ?vm$Context;
fakeTimers: ?FakeTimers<Timer>;
fakeTimers: ?FakeTimers;
global: ?Global;
moduleMocker: ?ModuleMocker;

Expand All @@ -49,28 +49,9 @@ class NodeEnvironment {
installCommonGlobals(global, config.globals);
this.moduleMocker = new mock.ModuleMocker(global);

const timerIdToRef = (id: number) => ({
id,
ref() {
return this;
},
unref() {
return this;
},
});

const timerRefToId = (timer: Timer): ?number => (timer && timer.id) || null;

const timerConfig = {
idToRef: timerIdToRef,
refToId: timerRefToId,
};

this.fakeTimers = new FakeTimers({
config,
global,
moduleMocker: this.moduleMocker,
timerConfig,
});
}

Expand Down
5 changes: 5 additions & 0 deletions packages/jest-jasmine2/src/__tests__/pTimeout.test.js
Expand Up @@ -13,6 +13,11 @@ jest.useFakeTimers();
import pTimeout from '../pTimeout';

describe('pTimeout', () => {
beforeEach(() => {
jest.spyOn(global, 'setTimeout');
jest.spyOn(global, 'clearTimeout');
});

it('calls `clearTimeout` and resolves when `promise` resolves.', async () => {
const onTimeout = jest.fn();
const promise = Promise.resolve();
Expand Down
3 changes: 2 additions & 1 deletion packages/jest-runtime/src/index.js
Expand Up @@ -1002,6 +1002,7 @@ class Runtime {
fn,
genMockFromModule: (moduleName: string) =>
this._generateMock(from, moduleName),
getRealSystemTime: () => _getFakeTimers().getRealSystemTime(),
getTimerCount: () => _getFakeTimers().getTimerCount(),
isMockFunction: this._moduleMocker.isMockFunction,
isolateModules,
Expand All @@ -1013,14 +1014,14 @@ class Runtime {
resetModules,
restoreAllMocks,
retryTimes,
runAllImmediates: () => _getFakeTimers().runAllImmediates(),
runAllTicks: () => _getFakeTimers().runAllTicks(),
runAllTimers: () => _getFakeTimers().runAllTimers(),
runOnlyPendingTimers: () => _getFakeTimers().runOnlyPendingTimers(),
runTimersToTime: (msToRun: number) =>
_getFakeTimers().advanceTimersByTime(msToRun),
setMock: (moduleName: string, mock: Object) =>
setMockFactory(moduleName, () => mock),
setSystemTime: (now?: number) => _getFakeTimers().setSystemTime(now),
setTimeout,
spyOn,
unmock,
Expand Down
1 change: 1 addition & 0 deletions packages/jest-util/package.json
Expand Up @@ -14,6 +14,7 @@
"graceful-fs": "^4.1.15",
"is-ci": "^2.0.0",
"jest-message-util": "^24.0.0",
"lolex": "^3.0.0",
"mkdirp": "^0.5.1",
"slash": "^2.0.0",
"source-map": "^0.6.0"
Expand Down

0 comments on commit a9bcc97

Please sign in to comment.