Skip to content

Commit 43988ff

Browse files
authored
feat(jest-runtime): expose @sinonjs/fake-timers async APIs (#13981)
1 parent d27e36f commit 43988ff

File tree

7 files changed

+328
-10
lines changed

7 files changed

+328
-10
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- `[jest-message-util]` Add support for [AggregateError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError) ([#13946](https://github.com/facebook/jest/pull/13946) & [#13947](https://github.com/facebook/jest/pull/13947))
1212
- `[jest-message-util]` Add support for [Error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) in `test` and `it` ([#13935](https://github.com/facebook/jest/pull/13935) & [#13966](https://github.com/facebook/jest/pull/13966))
1313
- `[jest-reporters]` Add `summaryThreshold` option to summary reporter to allow overriding the internal threshold that is used to print the summary of all failed tests when the number of test suites surpasses it ([#13895](https://github.com/facebook/jest/pull/13895))
14+
- `[jest-runtime]` Expose `@sinonjs/fake-timers` async APIs functions `advanceTimersByTimeAsync(msToRun)` (`tickAsync(msToRun)`), `advanceTimersToNextTimerAsync(steps)` (`nextAsync`), `runAllTimersAsync` (`runAllAsync`), and `runOnlyPendingTimersAsync` (`runToLastAsync`) ([#13981](https://github.com/facebook/jest/pull/13981))
1415
- `[jest-runtime, @jest/transform]` Allow V8 coverage provider to collect coverage from files which were not loaded explicitly ([#13974](https://github.com/facebook/jest/pull/13974))
1516
- `[jest-snapshot]` Add support to `cts` and `mts` TypeScript files to inline snapshots ([#13975](https://github.com/facebook/jest/pull/13975))
1617
- `[jest-worker]` Add `start` method to worker farms ([#13937](https://github.com/facebook/jest/pull/13937))

docs/JestObjectAPI.md

+40
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,16 @@ When this API is called, all pending macro-tasks and micro-tasks will be execute
921921

922922
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.
923923

924+
### `jest.runAllTimersAsync()`
925+
926+
Asynchronous equivalent of `jest.runAllTimers()`. It allows any scheduled promise callbacks to execute _before_ running the timers.
927+
928+
:::info
929+
930+
This function is not available when using legacy fake timers implementation.
931+
932+
:::
933+
924934
### `jest.runAllImmediates()`
925935

926936
Exhausts all tasks queued by `setImmediate()`.
@@ -937,18 +947,48 @@ Executes only the macro task queue (i.e. all tasks queued by `setTimeout()` or `
937947

938948
When this API is called, all timers are advanced by `msToRun` milliseconds. All pending "macro-tasks" that have been queued via `setTimeout()` or `setInterval()`, and would be executed within this time frame will be executed. Additionally, if those macro-tasks schedule new macro-tasks that would be executed within the same time frame, those will be executed until there are no more macro-tasks remaining in the queue, that should be run within `msToRun` milliseconds.
939949

950+
### `jest.advanceTimersByTimeAsync(msToRun)`
951+
952+
Asynchronous equivalent of `jest.advanceTimersByTime(msToRun)`. It allows any scheduled promise callbacks to execute _before_ running the timers.
953+
954+
:::info
955+
956+
This function is not available when using legacy fake timers implementation.
957+
958+
:::
959+
940960
### `jest.runOnlyPendingTimers()`
941961

942962
Executes only the macro-tasks that are currently pending (i.e., only the tasks that have been queued by `setTimeout()` or `setInterval()` up to this point). If any of the currently pending macro-tasks schedule new macro-tasks, those new tasks will not be executed by this call.
943963

944964
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.
945965

966+
### `jest.runOnlyPendingTimersAsync()`
967+
968+
Asynchronous equivalent of `jest.runOnlyPendingTimers()`. It allows any scheduled promise callbacks to execute _before_ running the timers.
969+
970+
:::info
971+
972+
This function is not available when using legacy fake timers implementation.
973+
974+
:::
975+
946976
### `jest.advanceTimersToNextTimer(steps)`
947977

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

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

982+
### `jest.advanceTimersToNextTimerAsync(steps)`
983+
984+
Asynchronous equivalent of `jest.advanceTimersToNextTimer(steps)`. It allows any scheduled promise callbacks to execute _before_ running the timers.
985+
986+
:::info
987+
988+
This function is not available when using legacy fake timers implementation.
989+
990+
:::
991+
952992
### `jest.clearAllTimers()`
953993

954994
Removes any pending timers from the timer system.

packages/jest-environment/src/index.ts

+36
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,28 @@ export interface Jest {
6060
* executed within this time frame will be executed.
6161
*/
6262
advanceTimersByTime(msToRun: number): void;
63+
/**
64+
* Advances all timers by `msToRun` milliseconds, firing callbacks if necessary.
65+
*
66+
* @remarks
67+
* Not available when using legacy fake timers implementation.
68+
*/
69+
advanceTimersByTimeAsync(msToRun: number): Promise<void>;
6370
/**
6471
* Advances all timers by the needed milliseconds so that only the next
6572
* timeouts/intervals will run. Optionally, you can provide steps, so it will
6673
* run steps amount of next timeouts/intervals.
6774
*/
6875
advanceTimersToNextTimer(steps?: number): void;
76+
/**
77+
* Advances the clock to the the moment of the first scheduled timer, firing it.
78+
* Optionally, you can provide steps, so it will run steps amount of
79+
* next timeouts/intervals.
80+
*
81+
* @remarks
82+
* Not available when using legacy fake timers implementation.
83+
*/
84+
advanceTimersToNextTimerAsync(steps?: number): Promise<void>;
6985
/**
7086
* Disables automatic mocking in the module loader.
7187
*/
@@ -298,13 +314,33 @@ export interface Jest {
298314
* and `setInterval()`).
299315
*/
300316
runAllTimers(): void;
317+
/**
318+
* Exhausts the macro-task queue (i.e., all tasks queued by `setTimeout()`
319+
* and `setInterval()`).
320+
*
321+
* @remarks
322+
* If new timers are added while it is executing they will be run as well.
323+
* @remarks
324+
* Not available when using legacy fake timers implementation.
325+
*/
326+
runAllTimersAsync(): Promise<void>;
301327
/**
302328
* Executes only the macro-tasks that are currently pending (i.e., only the
303329
* tasks that have been queued by `setTimeout()` or `setInterval()` up to this
304330
* point). If any of the currently pending macro-tasks schedule new
305331
* macro-tasks, those new tasks will not be executed by this call.
306332
*/
307333
runOnlyPendingTimers(): void;
334+
/**
335+
* Executes only the macro-tasks that are currently pending (i.e., only the
336+
* tasks that have been queued by `setTimeout()` or `setInterval()` up to this
337+
* point). If any of the currently pending macro-tasks schedule new
338+
* macro-tasks, those new tasks will not be executed by this call.
339+
*
340+
* @remarks
341+
* Not available when using legacy fake timers implementation.
342+
*/
343+
runOnlyPendingTimersAsync(): Promise<void>;
308344
/**
309345
* Explicitly supplies the mock object that the module system should return
310346
* for the specified module.

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

+131
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,137 @@ describe('FakeTimers', () => {
960960
});
961961
});
962962

963+
describe('advanceTimersToNextTimerAsync', () => {
964+
it('should advance the clock at the moment of the first scheduled timer', async () => {
965+
const global = {
966+
Date,
967+
Promise,
968+
clearTimeout,
969+
process,
970+
setTimeout,
971+
} as unknown as typeof globalThis;
972+
const timers = new FakeTimers({config: makeProjectConfig(), global});
973+
timers.useFakeTimers();
974+
timers.setSystemTime(0);
975+
976+
const spy = jest.fn();
977+
global.setTimeout(async () => {
978+
await Promise.resolve();
979+
global.setTimeout(spy, 100);
980+
}, 100);
981+
982+
await timers.advanceTimersToNextTimerAsync();
983+
expect(timers.now()).toBe(100);
984+
985+
await timers.advanceTimersToNextTimerAsync();
986+
expect(timers.now()).toBe(200);
987+
expect(spy).toHaveBeenCalled();
988+
});
989+
990+
it('should advance the clock at the moment of the n-th scheduled timer', async () => {
991+
const global = {
992+
Date,
993+
Promise,
994+
clearTimeout,
995+
process,
996+
setTimeout,
997+
} as unknown as typeof globalThis;
998+
const timers = new FakeTimers({config: makeProjectConfig(), global});
999+
timers.useFakeTimers();
1000+
timers.setSystemTime(0);
1001+
1002+
const spy = jest.fn();
1003+
global.setTimeout(async () => {
1004+
await Promise.resolve();
1005+
global.setTimeout(spy, 100);
1006+
}, 100);
1007+
1008+
await timers.advanceTimersToNextTimerAsync(2);
1009+
1010+
expect(timers.now()).toBe(200);
1011+
expect(spy).toHaveBeenCalled();
1012+
});
1013+
});
1014+
1015+
describe('runAllTimersAsync', () => {
1016+
it('should advance the clock to the last scheduled timer', async () => {
1017+
const global = {
1018+
Date,
1019+
Promise,
1020+
clearTimeout,
1021+
process,
1022+
setTimeout,
1023+
} as unknown as typeof globalThis;
1024+
const timers = new FakeTimers({config: makeProjectConfig(), global});
1025+
timers.useFakeTimers();
1026+
timers.setSystemTime(0);
1027+
1028+
const spy = jest.fn();
1029+
const spy2 = jest.fn();
1030+
global.setTimeout(async () => {
1031+
await Promise.resolve();
1032+
global.setTimeout(spy, 100);
1033+
global.setTimeout(spy2, 200);
1034+
}, 100);
1035+
1036+
await timers.runAllTimersAsync();
1037+
expect(timers.now()).toBe(300);
1038+
expect(spy).toHaveBeenCalled();
1039+
expect(spy2).toHaveBeenCalled();
1040+
});
1041+
});
1042+
1043+
describe('runOnlyPendingTimersAsync', () => {
1044+
it('should advance the clock to the last scheduled timer', async () => {
1045+
const global = {
1046+
Date,
1047+
Promise,
1048+
clearTimeout,
1049+
process,
1050+
setTimeout,
1051+
} as unknown as typeof globalThis;
1052+
const timers = new FakeTimers({config: makeProjectConfig(), global});
1053+
timers.useFakeTimers();
1054+
timers.setSystemTime(0);
1055+
1056+
const spy = jest.fn();
1057+
const spy2 = jest.fn();
1058+
global.setTimeout(spy, 50);
1059+
global.setTimeout(spy2, 50);
1060+
global.setTimeout(async () => {
1061+
await Promise.resolve();
1062+
}, 100);
1063+
1064+
await timers.runOnlyPendingTimersAsync();
1065+
expect(timers.now()).toBe(100);
1066+
expect(spy).toHaveBeenCalled();
1067+
expect(spy2).toHaveBeenCalled();
1068+
});
1069+
});
1070+
1071+
describe('advanceTimersByTimeAsync', () => {
1072+
it('should advance the clock', async () => {
1073+
const global = {
1074+
Date,
1075+
Promise,
1076+
clearTimeout,
1077+
process,
1078+
setTimeout,
1079+
} as unknown as typeof globalThis;
1080+
const timers = new FakeTimers({config: makeProjectConfig(), global});
1081+
timers.useFakeTimers();
1082+
1083+
const spy = jest.fn();
1084+
global.setTimeout(async () => {
1085+
await Promise.resolve();
1086+
global.setTimeout(spy, 100);
1087+
}, 100);
1088+
1089+
await timers.advanceTimersByTimeAsync(200);
1090+
expect(spy).toHaveBeenCalled();
1091+
});
1092+
});
1093+
9631094
describe('now', () => {
9641095
let timers: FakeTimers;
9651096
let fakedGlobal: typeof globalThis;

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

+32
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,24 @@ export default class FakeTimers {
5252
}
5353
}
5454

55+
async runAllTimersAsync(): Promise<void> {
56+
if (this._checkFakeTimers()) {
57+
await this._clock.runAllAsync();
58+
}
59+
}
60+
5561
runOnlyPendingTimers(): void {
5662
if (this._checkFakeTimers()) {
5763
this._clock.runToLast();
5864
}
5965
}
6066

67+
async runOnlyPendingTimersAsync(): Promise<void> {
68+
if (this._checkFakeTimers()) {
69+
await this._clock.runToLastAsync();
70+
}
71+
}
72+
6173
advanceTimersToNextTimer(steps = 1): void {
6274
if (this._checkFakeTimers()) {
6375
for (let i = steps; i > 0; i--) {
@@ -72,12 +84,32 @@ export default class FakeTimers {
7284
}
7385
}
7486

87+
async advanceTimersToNextTimerAsync(steps = 1): Promise<void> {
88+
if (this._checkFakeTimers()) {
89+
for (let i = steps; i > 0; i--) {
90+
await this._clock.nextAsync();
91+
// Fire all timers at this point: https://github.com/sinonjs/fake-timers/issues/250
92+
await this._clock.tickAsync(0);
93+
94+
if (this._clock.countTimers() === 0) {
95+
break;
96+
}
97+
}
98+
}
99+
}
100+
75101
advanceTimersByTime(msToRun: number): void {
76102
if (this._checkFakeTimers()) {
77103
this._clock.tick(msToRun);
78104
}
79105
}
80106

107+
async advanceTimersByTimeAsync(msToRun: number): Promise<void> {
108+
if (this._checkFakeTimers()) {
109+
await this._clock.tickAsync(msToRun);
110+
}
111+
}
112+
81113
runAllTicks(): void {
82114
if (this._checkFakeTimers()) {
83115
// @ts-expect-error - doesn't exist?

0 commit comments

Comments
 (0)