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

add jest.advanceTimersToNextTimer method #8713

Merged
merged 6 commits into from Jul 28, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -14,6 +14,7 @@
- `[jest-validate]` Allow `maxWorkers` as part of the `jest.config.js` ([#8565](https://github.com/facebook/jest/pull/8565))
- `[jest-runtime]` Allow passing configuration objects to transformers ([#7288](https://github.com/facebook/jest/pull/7288))
- `[@jest/core, @jest/test-sequencer]` Support async sort in custom `testSequencer` ([#8642](https://github.com/facebook/jest/pull/8642))
- `[jest-runtime, @jest/fake-timers]` Add `jest.advanceTimersToNextTimer` ([#8713](https://github.com/facebook/jest/pull/8713))

### Fixes

Expand Down
6 changes: 6 additions & 0 deletions docs/JestObjectAPI.md
Expand Up @@ -623,6 +623,12 @@ Executes only the macro-tasks that are currently pending (i.e., only the tasks t

This is useful for scenarios such as one where the module being tested schedules a `setTimeout()` whose callback schedules another `setTimeout()` recursively (meaning the scheduling never stops). In these scenarios, it's useful to be able to run forward in time by a single step at a time.

### `jest.advanceTimersToNextTimer(steps)`

Advances all timers by the needed milliseconds so that only the next timeouts/intervals will run.

Optionally, you can provide `steps`, so it will run `steps` amount of next timeouts/intervals.

### `jest.clearAllTimers()`

Removes any pending timers from the timer system.
Expand Down
5 changes: 5 additions & 0 deletions packages/jest-environment/src/index.ts
Expand Up @@ -65,6 +65,11 @@ export interface Jest {
/**
* Disables automatic mocking in the module loader.
*/
advanceTimersToNextTimer(steps?: number): void;
/**
eranshabi marked this conversation as resolved.
Show resolved Hide resolved
* Advances all timers by the needed milliseconds so that only the next timeouts/intervals will run.
* Optionally, you can provide steps, so it will run steps amount of next timeouts/intervals.
*/
autoMockOff(): Jest;
/**
* Enables automatic mocking in the module loader.
Expand Down
94 changes: 94 additions & 0 deletions packages/jest-fake-timers/src/__tests__/jestFakeTimers.test.ts
Expand Up @@ -651,6 +651,100 @@ describe('FakeTimers', () => {
});
});

describe('advanceTimersToNextTimer', () => {
SimenB marked this conversation as resolved.
Show resolved Hide resolved
it('runs timers in order', () => {
const global = ({process} as unknown) as NodeJS.Global;
const timers = new FakeTimers({
config,
global,
moduleMocker,
timerConfig,
});
timers.useFakeTimers();

const runOrder: Array<string> = [];
const mock1 = jest.fn(() => runOrder.push('mock1'));
const mock2 = jest.fn(() => runOrder.push('mock2'));
const mock3 = jest.fn(() => runOrder.push('mock3'));
const mock4 = jest.fn(() => runOrder.push('mock4'));

global.setTimeout(mock1, 100);
global.setTimeout(mock2, 0);
global.setTimeout(mock3, 0);
global.setInterval(() => {
mock4();
}, 200);

timers.advanceTimersToNextTimer();
// Move forward to t=0
expect(runOrder).toEqual(['mock2', 'mock3']);

timers.advanceTimersToNextTimer();
// Move forward to t=100
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1']);

timers.advanceTimersToNextTimer();
// Move forward to t=200
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4']);

timers.advanceTimersToNextTimer();
// Move forward to t=400
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4', 'mock4']);
});

it('run correct amount of steps', () => {
const global = ({process} as unknown) as NodeJS.Global;
const timers = new FakeTimers({
config,
global,
moduleMocker,
timerConfig,
});
timers.useFakeTimers();

const runOrder: Array<string> = [];
const mock1 = jest.fn(() => runOrder.push('mock1'));
const mock2 = jest.fn(() => runOrder.push('mock2'));
const mock3 = jest.fn(() => runOrder.push('mock3'));
const mock4 = jest.fn(() => runOrder.push('mock4'));

global.setTimeout(mock1, 100);
global.setTimeout(mock2, 0);
global.setTimeout(mock3, 0);
global.setInterval(() => {
mock4();
}, 200);

// Move forward to t=200
eranshabi marked this conversation as resolved.
Show resolved Hide resolved
timers.advanceTimersToNextTimer(2);
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1']);

// Move forward to t=600
timers.advanceTimersToNextTimer(3);
expect(runOrder).toEqual([
'mock2',
'mock3',
'mock1',
'mock4',
'mock4',
'mock4',
]);
});

it('does nothing when no timers have been scheduled', () => {
const global = ({process} as unknown) as NodeJS.Global;
const timers = new FakeTimers({
config,
global,
moduleMocker,
timerConfig,
});
timers.useFakeTimers();

timers.advanceTimersToNextTimer();
});
});

describe('reset', () => {
it('resets all pending setTimeouts', () => {
const global = ({process} as unknown) as NodeJS.Global;
Expand Down
14 changes: 14 additions & 0 deletions packages/jest-fake-timers/src/jestFakeTimers.ts
Expand Up @@ -237,6 +237,20 @@ export default class FakeTimers<TimerRef> {
.forEach(([timerHandle]) => this._runTimerHandle(timerHandle));
}

advanceTimersToNextTimer(steps: number = 1): void {
eranshabi marked this conversation as resolved.
Show resolved Hide resolved
const nextExpiry = Array.from(this._timers.values()).reduce(
SimenB marked this conversation as resolved.
Show resolved Hide resolved
(minExpiry: number | null, timer: Timer): number => {
if (minExpiry === null || timer.expiry < minExpiry) return timer.expiry;
return minExpiry;
},
null,
);
if (nextExpiry !== null && steps > 0) {
eranshabi marked this conversation as resolved.
Show resolved Hide resolved
this.advanceTimersByTime(nextExpiry - this._now);
this.advanceTimersToNextTimer(steps - 1);
}
}

advanceTimersByTime(msToRun: number) {
this._checkFakeTimers();
// Only run a generous number of timers and then bail.
Expand Down
2 changes: 2 additions & 0 deletions packages/jest-runtime/src/index.ts
Expand Up @@ -1037,6 +1037,8 @@ class Runtime {
this._environment.global.jasmine.addMatchers(matchers),
advanceTimersByTime: (msToRun: number) =>
_getFakeTimers().advanceTimersByTime(msToRun),
advanceTimersToNextTimer: () =>
eranshabi marked this conversation as resolved.
Show resolved Hide resolved
_getFakeTimers().advanceTimersToNextTimer(),
autoMockOff: disableAutomock,
autoMockOn: enableAutomock,
clearAllMocks,
Expand Down