Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Lolex to implement fake timers #5171

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ We skipped 24.2.0 because a draft was accidentally published. Please use `24.3.0
- `[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
Original file line number Diff line number Diff line change
Expand Up @@ -527,10 +527,6 @@ When this API is called, all pending macro-tasks and micro-tasks will be execute

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 @@ -557,6 +553,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.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,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);
});
});
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);
});
});
14 changes: 2 additions & 12 deletions packages/jest-environment-jsdom/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function isWin(globals: Win | Global.Global): globals is Win {

class JSDOMEnvironment implements JestEnvironment {
dom: JSDOM | null;
fakeTimers: FakeTimers<number> | null;
fakeTimers: FakeTimers | null;
// @ts-ignore
global: Global.Global | Win | null;
errorEventListener: ((event: Event & {error: Error}) => void) | null;
Expand Down Expand Up @@ -87,17 +87,7 @@ class JSDOMEnvironment implements JestEnvironment {

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

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

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

setup() {
Expand Down
33 changes: 2 additions & 31 deletions packages/jest-environment-node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,9 @@ import {installCommonGlobals} from 'jest-util';
import {JestFakeTimers as FakeTimers} from '@jest/fake-timers';
import {JestEnvironment} from '@jest/environment';

type Timer = {
id: number;
ref: () => Timer;
unref: () => Timer;
};

class NodeEnvironment implements JestEnvironment {
context: Context | null;
fakeTimers: FakeTimers<Timer> | null;
fakeTimers: FakeTimers | null;
global: Global.Global;
moduleMocker: ModuleMocker | null;

Expand Down Expand Up @@ -54,30 +48,7 @@ class NodeEnvironment implements JestEnvironment {
installCommonGlobals(global, config.globals);
this.moduleMocker = new ModuleMocker(global);

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

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

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

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

setup() {
Expand Down
9 changes: 4 additions & 5 deletions packages/jest-environment/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type ModuleWrapper = (...args: Array<unknown>) => unknown;
export declare class JestEnvironment {
constructor(config: Config.ProjectConfig, context?: EnvironmentContext);
global: Global.Global;
fakeTimers: FakeTimers<unknown> | null;
fakeTimers: FakeTimers | null;
moduleMocker: ModuleMocker | null;
runScript(
script: Script,
Expand Down Expand Up @@ -164,10 +164,6 @@ export interface Jest {
* retries is exhausted. This only works with `jest-circus`!
*/
retryTimes(numRetries: number): Jest;
/**
* Exhausts tasks queued by setImmediate().
*/
runAllImmediates(): void;
/**
* Exhausts the micro-task queue (usually interfaced in node via
* process.nextTick).
Expand Down Expand Up @@ -248,4 +244,7 @@ export interface Jest {
* every test so that local module state doesn't conflict between tests.
*/
isolateModules(fn: () => void): Jest;

getRealSystemTime(): number;
setSystemTime(now?: number): void;
}
6 changes: 5 additions & 1 deletion packages/jest-fake-timers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
"dependencies": {
"@jest/types": "^24.7.0",
"jest-message-util": "^24.7.1",
"jest-mock": "^24.7.0"
"jest-mock": "^24.7.0",
"lolex": "^4.0.0"
},
"devDependencies": {
"@types/lolex": "^3.1.1"
},
"engines": {
"node": ">= 6"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`FakeTimers runAllTimers warns when trying to advance timers while real timers are used 1`] = `
"A function to advance timers was called but the timers API is not mocked with fake timers. Call \`jest.useFakeTimers()\` in this test or enable fake timers globally by setting \`\\"timers\\": \\"fake\\"\` in the configuration file. This warning is likely a result of a default configuration change in Jest 15.

Release Blog Post: https://jestjs.io/blog/2016/09/01/jest-15.html"
`;
exports[`FakeTimers runAllTimers warns when trying to advance timers while real timers are used 1`] = `"A function to advance timers was called but the timers API is not mocked with fake timers. Call \`jest.useFakeTimers()\` in this test or enable fake timers globally by setting \`\\"timers\\": \\"fake\\"\` in the configuration file"`;
Loading