Skip to content

Commit f4bbdfc

Browse files
committed
add Lolex as alternate implementation of Fake Timers
1 parent 2780a77 commit f4bbdfc

File tree

29 files changed

+1143
-55
lines changed

29 files changed

+1143
-55
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- `[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))
99
- `[jest-circus]` Same as `[jest-jasmine2]`, only 5 tests will run concurrently by default ([#7770](https://github.com/facebook/jest/pull/7770))
1010
- `[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))
11+
- `[jest-util]` Add possibility to use Lolex as implementation of fake timers
1112

1213
### Fixes
1314

docs/Configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,8 @@ Default: `real`
10021002

10031003
Setting this value to `fake` allows the use of fake timers for functions such as `setTimeout`. Fake timers are useful when a piece of code sets a long timeout that we don't want to wait for in a test.
10041004

1005+
If the value is `lolex`, Lolex will be used as implementation instead of Jest's own. This will be the default fake implementation in a future major version of Jest.
1006+
10051007
### `transform` [object<string, string>]
10061008

10071009
Default: `undefined`

docs/JestObjectAPI.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,10 +408,12 @@ Restores all mocks back to their original value. Equivalent to calling [`.mockRe
408408

409409
## Mock timers
410410

411-
### `jest.useFakeTimers()`
411+
### `jest.useFakeTimers(string?)`
412412

413413
Instructs Jest to use fake versions of the standard timer functions (`setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`, `nextTick`, `setImmediate` and `clearImmediate`).
414414

415+
If you pass `'lolex'` as argument, Lolex will be used as implementation instead of Jest's own fake timers.
416+
415417
Returns the `jest` object for chaining.
416418

417419
### `jest.useRealTimers()`
@@ -438,6 +440,8 @@ This is often useful for synchronously executing setTimeouts during a test in or
438440

439441
Exhausts all tasks queued by `setImmediate()`.
440442

443+
> Note: This function is not available when using Lolex as fake timers implementation
444+
441445
### `jest.advanceTimersByTime(msToRun)`
442446

443447
##### renamed in Jest **22.0.0+**
@@ -464,6 +468,18 @@ This means, if any timers have been scheduled (but have not yet executed), they
464468

465469
Returns the number of fake timers still left to run.
466470

471+
### `.jest.setSystemTime()`
472+
473+
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()`.
474+
475+
> Note: This function is only available when using Lolex as fake timers implementation
476+
477+
### `.jest.getRealSystemTime()`
478+
479+
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.
480+
481+
> Note: This function is only available when using Lolex as fake timers implementation
482+
467483
## Misc
468484

469485
### `jest.setTimeout(timeout)`

e2e/__tests__/lolex.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import runJest from '../runJest';
11+
12+
describe('Lolex as implementation fo fake timers', () => {
13+
it('should be possible to use Lolex from config', () => {
14+
const result = runJest('lolex/from-config');
15+
expect(result.status).toBe(0);
16+
});
17+
18+
it('should be possible to use Lolex from jest-object', () => {
19+
const result = runJest('lolex/from-jest-object');
20+
expect(result.status).toBe(0);
21+
});
22+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
test('fake timers', () => {
11+
jest.setSystemTime(0);
12+
13+
expect(Date.now()).toBe(0);
14+
15+
jest.setSystemTime(1000);
16+
17+
expect(Date.now()).toBe(1000);
18+
});

e2e/lolex/from-config/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"jest": {
3+
"timers": "lolex",
4+
"testEnvironment": "node"
5+
}
6+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
test('fake timers', () => {
11+
jest.useFakeTimers('lolex');
12+
13+
jest.setSystemTime(0);
14+
15+
expect(Date.now()).toBe(0);
16+
17+
jest.setSystemTime(1000);
18+
19+
expect(Date.now()).toBe(1000);
20+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"jest": {
3+
"testEnvironment": "node"
4+
}
5+
}

e2e/timer-reset-mocks/after-reset-all-mocks/timerAndMock.test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ describe('timers', () => {
44
it('should work before calling resetAllMocks', () => {
55
jest.useFakeTimers();
66
const f = jest.fn();
7-
setImmediate(() => f());
8-
jest.runAllImmediates();
9-
expect(f.mock.calls.length).toBe(1);
7+
setTimeout(f, 0);
8+
jest.runAllTimers();
9+
expect(f).toHaveBeenCalledTimes(1);
1010
});
1111

1212
it('should not break after calling resetAllMocks', () => {
1313
jest.resetAllMocks();
1414
jest.useFakeTimers();
1515
const f = jest.fn();
16-
setImmediate(() => f());
17-
jest.runAllImmediates();
18-
expect(f.mock.calls.length).toBe(1);
16+
setTimeout(f, 0);
17+
jest.runAllTimers();
18+
expect(f).toHaveBeenCalledTimes(1);
1919
});
2020
});

e2e/timer-reset-mocks/with-reset-mocks/timerWithMock.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ describe('timers', () => {
44
it('should work before calling resetAllMocks', () => {
55
const f = jest.fn();
66
jest.useFakeTimers();
7-
setImmediate(() => f());
8-
jest.runAllImmediates();
9-
expect(f.mock.calls.length).toBe(1);
7+
setTimeout(f, 0);
8+
jest.runAllTimers();
9+
expect(f).toHaveBeenCalledTimes(1);
1010
});
1111
});

examples/timer/__tests__/infinite_timer_game.test.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@
55
jest.useFakeTimers();
66

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

1112
infiniteTimerGame(callback);
1213

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

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

2526
// And it should have created a new timer to start the game over in
2627
// 10 seconds
27-
expect(setTimeout.mock.calls.length).toBe(2);
28-
expect(setTimeout.mock.calls[1][1]).toBe(10000);
28+
expect(setTimeout).toBeCalledTimes(2);
29+
expect(setTimeout).toHaveBeenNthCalledWith(2, expect.any(Function), 10000);
2930
});

examples/timer/__tests__/timer_game.test.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
jest.useFakeTimers();
66

77
describe('timerGame', () => {
8+
beforeEach(() => {
9+
jest.spyOn(global, 'setTimeout');
10+
});
811
it('waits 1 second before ending the game', () => {
912
const timerGame = require('../timerGame');
1013
timerGame();
1114

12-
expect(setTimeout.mock.calls.length).toBe(1);
13-
expect(setTimeout.mock.calls[0][1]).toBe(1000);
15+
expect(setTimeout).toBeCalledTimes(1);
16+
expect(setTimeout).toBeCalledWith(expect.any(Function), 1000);
1417
});
1518

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

2831
// Now our callback should have been called!
2932
expect(callback).toBeCalled();
30-
expect(callback.mock.calls.length).toBe(1);
33+
expect(callback).toBeCalledTimes(1);
3134
});
3235

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

4548
// Now our callback should have been called!
4649
expect(callback).toBeCalled();
47-
expect(callback.mock.calls.length).toBe(1);
50+
expect(callback).toBeCalledTimes(1);
4851
});
4952
});

flow-typed/npm/lolex_v2.x.x.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// flow-typed signature: a865cf1b7ee719c2a40b85dc8dccf56c
2+
// flow-typed version: fc3f3a2e99/lolex_v2.x.x/flow_>=v0.64.x
3+
4+
// @flow
5+
declare module 'lolex' {
6+
declare opaque type ImmediateID;
7+
declare type installConfig = {
8+
target?: Object,
9+
now?: number | Date,
10+
toFake?: string[],
11+
loopLimit?: number,
12+
shouldAdvanceTime?: boolean,
13+
advanceTimeDelta?: number,
14+
};
15+
declare type lolex = {
16+
createClock(now?: number, loopLimit?: number): Clock,
17+
install(config?: installConfig): Clock,
18+
timers: Object,
19+
withGlobal(global: Object): lolex,
20+
};
21+
declare type Clock = {
22+
setTimeout: typeof setTimeout;
23+
clearTimeout: typeof clearTimeout;
24+
setInterval: typeof setInterval;
25+
clearInterval: typeof clearInterval;
26+
setImmediate: typeof setImmediate;
27+
clearImmediate: typeof clearImmediate;
28+
requestAnimationFrame: typeof requestAnimationFrame;
29+
cancelAnimationFrame: typeof cancelAnimationFrame;
30+
hrtime: typeof process.hrtime;
31+
nextTick: typeof process.nextTick;
32+
now: number;
33+
performance?: {
34+
now: typeof performance.now,
35+
};
36+
tick(time: number | string): void;
37+
next(): void;
38+
reset(): void;
39+
runAll(): void;
40+
runMicrotasks(): void;
41+
runToFrame(): void;
42+
runToLast(): void;
43+
setSystemTime(now?: number | Date): void;
44+
uninstall(): Object[];
45+
Date: typeof Date;
46+
Performance: typeof Performance;
47+
countTimers(): number;
48+
}
49+
declare module.exports: lolex;
50+
}

packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapter.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ const jestAdapter = async (
5151
environment.fakeTimers.useFakeTimers();
5252
}
5353

54+
if (config.timers === 'lolex') {
55+
environment.fakeTimersLolex.useFakeTimers();
56+
}
57+
5458
globals.beforeEach(() => {
5559
if (config.resetModules) {
5660
runtime.resetModules();

packages/jest-environment-jsdom/src/index.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import type {EnvironmentContext} from 'types/Environment';
1212
import type {Global} from 'types/Global';
1313
import type {ModuleMocker} from 'jest-mock';
1414

15-
import {FakeTimers, installCommonGlobals} from 'jest-util';
15+
import {FakeTimers, FakeTimersLolex, installCommonGlobals} from 'jest-util';
1616
import mock from 'jest-mock';
1717
import {JSDOM, VirtualConsole} from 'jsdom';
1818

1919
class JSDOMEnvironment {
2020
dom: ?Object;
2121
fakeTimers: ?FakeTimers<number>;
22+
fakeTimersLolex: ?FakeTimersLolex;
2223
global: ?Global;
2324
errorEventListener: ?Function;
2425
moduleMocker: ?ModuleMocker;
@@ -76,6 +77,11 @@ class JSDOMEnvironment {
7677
moduleMocker: this.moduleMocker,
7778
timerConfig,
7879
});
80+
81+
this.fakeTimersLolex = new FakeTimersLolex({
82+
config,
83+
global,
84+
});
7985
}
8086

8187
setup(): Promise<void> {
@@ -86,6 +92,9 @@ class JSDOMEnvironment {
8692
if (this.fakeTimers) {
8793
this.fakeTimers.dispose();
8894
}
95+
if (this.fakeTimersLolex) {
96+
this.fakeTimersLolex.dispose();
97+
}
8998
if (this.global) {
9099
if (this.errorEventListener) {
91100
this.global.removeEventListener('error', this.errorEventListener);
@@ -98,6 +107,7 @@ class JSDOMEnvironment {
98107
this.global = null;
99108
this.dom = null;
100109
this.fakeTimers = null;
110+
this.fakeTimersLolex = null;
101111
return Promise.resolve();
102112
}
103113

packages/jest-environment-node/src/index.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type {Global} from 'types/Global';
1313
import type {ModuleMocker} from 'jest-mock';
1414

1515
import vm from 'vm';
16-
import {FakeTimers, installCommonGlobals} from 'jest-util';
16+
import {FakeTimers, FakeTimersLolex, installCommonGlobals} from 'jest-util';
1717
import mock from 'jest-mock';
1818

1919
type Timer = {|
@@ -25,6 +25,7 @@ type Timer = {|
2525
class NodeEnvironment {
2626
context: ?vm$Context;
2727
fakeTimers: ?FakeTimers<Timer>;
28+
fakeTimersLolex: ?FakeTimersLolex;
2829
global: ?Global;
2930
moduleMocker: ?ModuleMocker;
3031

@@ -72,6 +73,11 @@ class NodeEnvironment {
7273
moduleMocker: this.moduleMocker,
7374
timerConfig,
7475
});
76+
77+
this.fakeTimersLolex = new FakeTimersLolex({
78+
config,
79+
global,
80+
});
7581
}
7682

7783
setup(): Promise<void> {
@@ -82,8 +88,12 @@ class NodeEnvironment {
8288
if (this.fakeTimers) {
8389
this.fakeTimers.dispose();
8490
}
91+
if (this.fakeTimersLolex) {
92+
this.fakeTimersLolex.dispose();
93+
}
8594
this.context = null;
8695
this.fakeTimers = null;
96+
this.fakeTimersLolex = null;
8797
return Promise.resolve();
8898
}
8999

packages/jest-jasmine2/src/__tests__/pTimeout.test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ jest.useFakeTimers();
1313
import pTimeout from '../pTimeout';
1414

1515
describe('pTimeout', () => {
16+
beforeEach(() => {
17+
jest.spyOn(global, 'setTimeout');
18+
jest.spyOn(global, 'clearTimeout');
19+
});
20+
1621
it('calls `clearTimeout` and resolves when `promise` resolves.', async () => {
1722
const onTimeout = jest.fn();
1823
const promise = Promise.resolve();

0 commit comments

Comments
 (0)