Skip to content

Commit a5a1a59

Browse files
eranshabiSimenB
authored andcommitted
feat: add jest.advanceTimersToNextTimer method (#8713)
1 parent dcc1918 commit a5a1a59

File tree

6 files changed

+153
-1
lines changed

6 files changed

+153
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- `[jest-validate]` Allow `maxWorkers` as part of the `jest.config.js` ([#8565](https://github.com/facebook/jest/pull/8565))
1818
- `[jest-runtime]` Allow passing configuration objects to transformers ([#7288](https://github.com/facebook/jest/pull/7288))
1919
- `[@jest/core, @jest/test-sequencer]` Support async sort in custom `testSequencer` ([#8642](https://github.com/facebook/jest/pull/8642))
20+
- `[jest-runtime, @jest/fake-timers]` Add `jest.advanceTimersToNextTimer` ([#8713](https://github.com/facebook/jest/pull/8713))
2021
- `[@jest-transform]` Extract transforming require logic within `jest-core` into `@jest-transform` ([#8756](https://github.com/facebook/jest/pull/8756))
2122

2223
### Fixes

docs/JestObjectAPI.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,12 @@ Executes only the macro-tasks that are currently pending (i.e., only the tasks t
623623

624624
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.
625625

626+
### `jest.advanceTimersToNextTimer(steps)`
627+
628+
Advances all timers by the needed milliseconds so that only the next timeouts/intervals will run.
629+
630+
Optionally, you can provide `steps`, so it will run `steps` amount of next timeouts/intervals.
631+
626632
### `jest.clearAllTimers()`
627633

628634
Removes any pending timers from the timer system.

packages/jest-environment/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ export interface Jest {
6262
* @deprecated Use `expect.extend` instead
6363
*/
6464
addMatchers(matchers: Record<string, any>): void;
65+
/**
66+
* Advances all timers by the needed milliseconds so that only the next timeouts/intervals will run.
67+
* Optionally, you can provide steps, so it will run steps amount of next timeouts/intervals.
68+
*/
69+
advanceTimersToNextTimer(steps?: number): void;
6570
/**
6671
* Disables automatic mocking in the module loader.
6772
*/

packages/jest-fake-timers/src/__tests__/jestFakeTimers.test.ts

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,6 @@ describe('FakeTimers', () => {
624624

625625
timers.advanceTimersByTime(100);
626626
});
627-
628627
it('throws before allowing infinite recursion', () => {
629628
const global = ({process} as unknown) as NodeJS.Global;
630629
const timers = new FakeTimers({
@@ -651,6 +650,128 @@ describe('FakeTimers', () => {
651650
});
652651
});
653652

653+
describe('advanceTimersToNextTimer', () => {
654+
it('runs timers in order', () => {
655+
const global = ({process} as unknown) as NodeJS.Global;
656+
const timers = new FakeTimers({
657+
config,
658+
global,
659+
moduleMocker,
660+
timerConfig,
661+
});
662+
timers.useFakeTimers();
663+
664+
const runOrder: Array<string> = [];
665+
const mock1 = jest.fn(() => runOrder.push('mock1'));
666+
const mock2 = jest.fn(() => runOrder.push('mock2'));
667+
const mock3 = jest.fn(() => runOrder.push('mock3'));
668+
const mock4 = jest.fn(() => runOrder.push('mock4'));
669+
670+
global.setTimeout(mock1, 100);
671+
global.setTimeout(mock2, 0);
672+
global.setTimeout(mock3, 0);
673+
global.setInterval(() => {
674+
mock4();
675+
}, 200);
676+
677+
timers.advanceTimersToNextTimer();
678+
// Move forward to t=0
679+
expect(runOrder).toEqual(['mock2', 'mock3']);
680+
681+
timers.advanceTimersToNextTimer();
682+
// Move forward to t=100
683+
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1']);
684+
685+
timers.advanceTimersToNextTimer();
686+
// Move forward to t=200
687+
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4']);
688+
689+
timers.advanceTimersToNextTimer();
690+
// Move forward to t=400
691+
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4', 'mock4']);
692+
});
693+
694+
it('run correct amount of steps', () => {
695+
const global = ({process} as unknown) as NodeJS.Global;
696+
const timers = new FakeTimers({
697+
config,
698+
global,
699+
moduleMocker,
700+
timerConfig,
701+
});
702+
timers.useFakeTimers();
703+
704+
const runOrder: Array<string> = [];
705+
const mock1 = jest.fn(() => runOrder.push('mock1'));
706+
const mock2 = jest.fn(() => runOrder.push('mock2'));
707+
const mock3 = jest.fn(() => runOrder.push('mock3'));
708+
const mock4 = jest.fn(() => runOrder.push('mock4'));
709+
710+
global.setTimeout(mock1, 100);
711+
global.setTimeout(mock2, 0);
712+
global.setTimeout(mock3, 0);
713+
global.setInterval(() => {
714+
mock4();
715+
}, 200);
716+
717+
// Move forward to t=100
718+
timers.advanceTimersToNextTimer(2);
719+
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1']);
720+
721+
// Move forward to t=600
722+
timers.advanceTimersToNextTimer(3);
723+
expect(runOrder).toEqual([
724+
'mock2',
725+
'mock3',
726+
'mock1',
727+
'mock4',
728+
'mock4',
729+
'mock4',
730+
]);
731+
});
732+
733+
it('setTimeout inside setTimeout', () => {
734+
const global = ({process} as unknown) as NodeJS.Global;
735+
const timers = new FakeTimers({
736+
config,
737+
global,
738+
moduleMocker,
739+
timerConfig,
740+
});
741+
timers.useFakeTimers();
742+
743+
const runOrder: Array<string> = [];
744+
const mock1 = jest.fn(() => runOrder.push('mock1'));
745+
const mock2 = jest.fn(() => runOrder.push('mock2'));
746+
const mock3 = jest.fn(() => runOrder.push('mock3'));
747+
const mock4 = jest.fn(() => runOrder.push('mock4'));
748+
749+
global.setTimeout(mock1, 0);
750+
global.setTimeout(() => {
751+
mock2();
752+
global.setTimeout(mock3, 50);
753+
}, 25);
754+
global.setTimeout(mock4, 100);
755+
756+
// Move forward to t=75
757+
timers.advanceTimersToNextTimer(3);
758+
expect(runOrder).toEqual(['mock1', 'mock2', 'mock3']);
759+
});
760+
761+
it('does nothing when no timers have been scheduled', () => {
762+
const global = ({process} as unknown) as NodeJS.Global;
763+
const timers = new FakeTimers({
764+
config,
765+
global,
766+
moduleMocker,
767+
timerConfig,
768+
});
769+
timers.useFakeTimers();
770+
771+
timers.advanceTimersToNextTimer();
772+
});
773+
});
774+
654775
describe('reset', () => {
655776
it('resets all pending setTimeouts', () => {
656777
const global = ({process} as unknown) as NodeJS.Global;

packages/jest-fake-timers/src/jestFakeTimers.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,23 @@ export default class FakeTimers<TimerRef> {
237237
.forEach(([timerHandle]) => this._runTimerHandle(timerHandle));
238238
}
239239

240+
advanceTimersToNextTimer(steps = 1) {
241+
if (steps < 1) {
242+
return;
243+
}
244+
const nextExpiry = Array.from(this._timers.values()).reduce(
245+
(minExpiry: number | null, timer: Timer): number => {
246+
if (minExpiry === null || timer.expiry < minExpiry) return timer.expiry;
247+
return minExpiry;
248+
},
249+
null,
250+
);
251+
if (nextExpiry !== null) {
252+
this.advanceTimersByTime(nextExpiry - this._now);
253+
this.advanceTimersToNextTimer(steps - 1);
254+
}
255+
}
256+
240257
advanceTimersByTime(msToRun: number) {
241258
this._checkFakeTimers();
242259
// Only run a generous number of timers and then bail.

packages/jest-runtime/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,8 @@ class Runtime {
10371037
this._environment.global.jasmine.addMatchers(matchers),
10381038
advanceTimersByTime: (msToRun: number) =>
10391039
_getFakeTimers().advanceTimersByTime(msToRun),
1040+
advanceTimersToNextTimer: (steps?: number) =>
1041+
_getFakeTimers().advanceTimersToNextTimer(steps),
10401042
autoMockOff: disableAutomock,
10411043
autoMockOn: enableAutomock,
10421044
clearAllMocks,

0 commit comments

Comments
 (0)