Skip to content

Commit 40187bd

Browse files
authoredJan 16, 2023
feat: add runAllTimersAsync from sinonjs (#2209)
close #1804
1 parent 6145d7b commit 40187bd

File tree

4 files changed

+501
-0
lines changed

4 files changed

+501
-0
lines changed
 

‎docs/api/index.md

+65
Original file line numberDiff line numberDiff line change
@@ -2553,6 +2553,19 @@ Vitest provides utility functions to help you out through it's **vi** helper. Yo
25532553
vi.advanceTimersByTime(150)
25542554
```
25552555
2556+
### vi.advanceTimersByTimeAsync
2557+
2558+
- **Type:** `(ms: number) => Promise<Vitest>`
2559+
2560+
Works just like `runAllTimersAsync`, but will end after passed milliseconds. This will include asynchronously set timers. For example this will log `1, 2, 3` and will not throw:
2561+
2562+
```ts
2563+
let i = 0
2564+
setInterval(() => Promise.resolve().then(() => console.log(++i)), 50)
2565+
2566+
await vi.advanceTimersByTimeAsync(150)
2567+
```
2568+
25562569
### vi.advanceTimersToNextTimer
25572570
25582571
- **Type:** `() => Vitest`
@@ -2568,6 +2581,21 @@ Vitest provides utility functions to help you out through it's **vi** helper. Yo
25682581
.advanceTimersToNextTimer() // log 3
25692582
```
25702583
2584+
### vi.advanceTimersToNextTimerAsync
2585+
2586+
- **Type:** `() => Promise<Vitest>`
2587+
2588+
Will call next available timer even if it was set asynchronously. Useful to make assertions between each timer call. You can chain call it to manage timers by yourself.
2589+
2590+
```ts
2591+
let i = 0
2592+
setInterval(() => Promise.resolve().then(() => console.log(++i)), 50)
2593+
2594+
vi.advanceTimersToNextTimerAsync() // log 1
2595+
.advanceTimersToNextTimerAsync() // log 2
2596+
.advanceTimersToNextTimerAsync() // log 3
2597+
```
2598+
25712599
### vi.getTimerCount
25722600
25732601
- **Type:** `() => number`
@@ -2984,6 +3012,21 @@ IntersectionObserver === undefined
29843012
vi.runAllTimers()
29853013
```
29863014
3015+
### vi.runAllTimersAsync
3016+
3017+
- **Type:** `() => Promise<Vitest>`
3018+
3019+
This method will asynchronously invoke every initiated timer until the timers queue is empty. It means that every timer called during `runAllTimersAsync` will be fired even asynchronous timers. If you have an infinite interval,
3020+
it will throw after 10 000 tries. For example this will log `result`:
3021+
3022+
```ts
3023+
setTimeout(async () => {
3024+
console.log(await Promise.resolve('result'))
3025+
}, 100)
3026+
3027+
await vi.runAllTimersAsync()
3028+
```
3029+
29873030
### vi.runOnlyPendingTimers
29883031
29893032
- **Type:** `() => Vitest`
@@ -2997,6 +3040,28 @@ IntersectionObserver === undefined
29973040
vi.runOnlyPendingTimers()
29983041
```
29993042
3043+
### vi.runOnlyPendingTimersAsync
3044+
3045+
- **Type:** `() => Promise<Vitest>`
3046+
3047+
This method will asynchronously call every timer that was initiated after `vi.useFakeTimers()` call, even asynchronous ones. It will not fire any timer that was initiated during its call. For example this will log `2, 3, 3, 1`:
3048+
3049+
```ts
3050+
setTimeout(() => {
3051+
console.log(1)
3052+
}, 100)
3053+
setTimeout(() => {
3054+
Promise.resolve().then(() => {
3055+
console.log(2)
3056+
setInterval(() => {
3057+
console.log(3)
3058+
}, 40)
3059+
})
3060+
}, 10)
3061+
3062+
await vi.runOnlyPendingTimersAsync()
3063+
```
3064+
30003065
### vi.setSystemTime
30013066
30023067
- **Type**: `(date: string | number | Date) => void`

‎packages/vitest/src/integrations/mock/timers.ts

+28
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,21 @@ export class FakeTimers {
5252
this._clock.runAll()
5353
}
5454

55+
async runAllTimersAsync(): Promise<void> {
56+
if (this._checkFakeTimers())
57+
await this._clock.runAllAsync()
58+
}
59+
5560
runOnlyPendingTimers(): void {
5661
if (this._checkFakeTimers())
5762
this._clock.runToLast()
5863
}
5964

65+
async runOnlyPendingTimersAsync(): Promise<void> {
66+
if (this._checkFakeTimers())
67+
await this._clock.runToLastAsync()
68+
}
69+
6070
advanceTimersToNextTimer(steps = 1): void {
6171
if (this._checkFakeTimers()) {
6272
for (let i = steps; i > 0; i--) {
@@ -70,11 +80,29 @@ export class FakeTimers {
7080
}
7181
}
7282

83+
async advanceTimersToNextTimerAsync(steps = 1): Promise<void> {
84+
if (this._checkFakeTimers()) {
85+
for (let i = steps; i > 0; i--) {
86+
await this._clock.nextAsync()
87+
// Fire all timers at this point: https://github.com/sinonjs/fake-timers/issues/250
88+
this._clock.tick(0)
89+
90+
if (this._clock.countTimers() === 0)
91+
break
92+
}
93+
}
94+
}
95+
7396
advanceTimersByTime(msToRun: number): void {
7497
if (this._checkFakeTimers())
7598
this._clock.tick(msToRun)
7699
}
77100

101+
async advanceTimersByTimeAsync(msToRun: number): Promise<void> {
102+
if (this._checkFakeTimers())
103+
await this._clock.tickAsync(msToRun)
104+
}
105+
78106
runAllTicks(): void {
79107
if (this._checkFakeTimers()) {
80108
// @ts-expect-error method not exposed

‎packages/vitest/src/integrations/vi.ts

+20
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,21 @@ class VitestUtils {
6161
return this
6262
}
6363

64+
public async runOnlyPendingTimersAsync() {
65+
await this._timers.runOnlyPendingTimersAsync()
66+
return this
67+
}
68+
6469
public runAllTimers() {
6570
this._timers.runAllTimers()
6671
return this
6772
}
6873

74+
public async runAllTimersAsync() {
75+
await this._timers.runAllTimersAsync()
76+
return this
77+
}
78+
6979
public runAllTicks() {
7080
this._timers.runAllTicks()
7181
return this
@@ -76,11 +86,21 @@ class VitestUtils {
7686
return this
7787
}
7888

89+
public async advanceTimersByTimeAsync(ms: number) {
90+
await this._timers.advanceTimersByTimeAsync(ms)
91+
return this
92+
}
93+
7994
public advanceTimersToNextTimer() {
8095
this._timers.advanceTimersToNextTimer()
8196
return this
8297
}
8398

99+
public async advanceTimersToNextTimerAsync() {
100+
await this._timers.advanceTimersToNextTimerAsync()
101+
return this
102+
}
103+
84104
public getTimerCount() {
85105
return this._timers.getTimerCount()
86106
}

‎test/core/test/timers.test.ts

+388
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,136 @@ describe('FakeTimers', () => {
336336
})
337337
})
338338

339+
describe('runAllTimersAsync', () => {
340+
it('runs all timers in order', async () => {
341+
const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise }
342+
const timers = new FakeTimers({ global })
343+
timers.useFakeTimers()
344+
345+
const runOrder = []
346+
const mock1 = vi.fn(() => runOrder.push('mock1'))
347+
const mock2 = vi.fn(() => runOrder.push('mock2'))
348+
const mock3 = vi.fn(() => runOrder.push('mock3'))
349+
const mock4 = vi.fn(() => runOrder.push('mock4'))
350+
const mock5 = vi.fn(() => runOrder.push('mock5'))
351+
const mock6 = vi.fn(() => runOrder.push('mock6'))
352+
353+
global.setTimeout(mock1, 100)
354+
global.setTimeout(mock2, NaN)
355+
global.setTimeout(mock3, 0)
356+
const intervalHandler = global.setInterval(() => {
357+
mock4()
358+
global.clearInterval(intervalHandler)
359+
}, 200)
360+
global.setTimeout(mock5, Infinity)
361+
global.setTimeout(mock6, -Infinity)
362+
363+
await timers.runAllTimersAsync()
364+
expect(runOrder).toEqual([
365+
'mock2',
366+
'mock3',
367+
'mock5',
368+
'mock6',
369+
'mock1',
370+
'mock4',
371+
])
372+
})
373+
374+
it('warns when trying to advance timers while real timers are used', async () => {
375+
const timers = new FakeTimers({
376+
config: {
377+
rootDir: __dirname,
378+
},
379+
global,
380+
})
381+
await expect(timers.runAllTimersAsync()).rejects.toThrow(/Timers are not mocked/)
382+
})
383+
384+
it('only runs a setTimeout callback once (ever)', async () => {
385+
const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise }
386+
const timers = new FakeTimers({ global })
387+
timers.useFakeTimers()
388+
389+
const fn = vi.fn()
390+
global.setTimeout(fn, 0)
391+
expect(fn).toHaveBeenCalledTimes(0)
392+
393+
await timers.runAllTimersAsync()
394+
expect(fn).toHaveBeenCalledTimes(1)
395+
396+
await timers.runAllTimersAsync()
397+
expect(fn).toHaveBeenCalledTimes(1)
398+
})
399+
400+
it('runs callbacks with arguments after the interval', async () => {
401+
const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise }
402+
const timers = new FakeTimers({ global })
403+
timers.useFakeTimers()
404+
405+
const fn = vi.fn()
406+
global.setTimeout(fn, 0, 'mockArg1', 'mockArg2')
407+
408+
await timers.runAllTimersAsync()
409+
expect(fn).toHaveBeenCalledTimes(1)
410+
expect(fn).toHaveBeenCalledWith('mockArg1', 'mockArg2')
411+
})
412+
413+
it('throws before allowing infinite recursion', async () => {
414+
const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise }
415+
const timers = new FakeTimers({ global, config: { loopLimit: 20 } })
416+
timers.useFakeTimers()
417+
418+
global.setTimeout(function infinitelyRecursingCallback() {
419+
global.setTimeout(infinitelyRecursingCallback, 0)
420+
}, 0)
421+
422+
await expect(
423+
timers.runAllTimersAsync(),
424+
).rejects.toThrow(
425+
'Aborting after running 20 timers, assuming an infinite loop!',
426+
)
427+
})
428+
429+
it('also clears ticks', async () => {
430+
const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise }
431+
const timers = new FakeTimers({ global })
432+
timers.useFakeTimers()
433+
434+
const fn = vi.fn()
435+
global.setTimeout(() => {
436+
process.nextTick(fn)
437+
}, 0)
438+
expect(fn).toHaveBeenCalledTimes(0)
439+
440+
await timers.runAllTimersAsync()
441+
expect(fn).toHaveBeenCalledTimes(1)
442+
})
443+
444+
it('all callbacks are called when setTimeout calls asynchronous method', async () => {
445+
const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise }
446+
const timers = new FakeTimers({ global })
447+
timers.useFakeTimers()
448+
449+
const runOrder = []
450+
const mock2 = vi.fn(async () => {
451+
runOrder.push('mock2')
452+
return global.Promise.resolve(true)
453+
})
454+
const mock1 = vi.fn(async () => {
455+
await mock2()
456+
runOrder.push('mock1')
457+
})
458+
459+
global.setTimeout(mock1, 100)
460+
await timers.runAllTimersAsync()
461+
462+
expect(runOrder).toEqual([
463+
'mock2',
464+
'mock1',
465+
])
466+
})
467+
})
468+
339469
describe('advanceTimersByTime', () => {
340470
it('runs timers in order', () => {
341471
const global = { Date: FakeDate, clearTimeout, process, setTimeout }
@@ -385,6 +515,55 @@ describe('FakeTimers', () => {
385515
})
386516
})
387517

518+
describe('advanceTimersByTimeAsync', () => {
519+
it('runs timers in order', async () => {
520+
const global = { Date: FakeDate, clearTimeout, clearInterval, process, setTimeout, setInterval, Promise }
521+
const timers = new FakeTimers({ global })
522+
timers.useFakeTimers()
523+
524+
const runOrder = []
525+
const mock1 = vi.fn(() => runOrder.push('mock1'))
526+
const mock2 = vi.fn(() => runOrder.push('mock2'))
527+
const mock3 = vi.fn(() => runOrder.push('mock3'))
528+
const mock4 = vi.fn(() => runOrder.push('mock4'))
529+
530+
global.setTimeout(mock1, 100)
531+
global.setTimeout(mock2, 0)
532+
global.setTimeout(mock3, 0)
533+
global.setInterval(() => {
534+
mock4()
535+
}, 200)
536+
537+
// Move forward to t=50
538+
await timers.advanceTimersByTimeAsync(50)
539+
expect(runOrder).toEqual(['mock2', 'mock3'])
540+
541+
// Move forward to t=60
542+
await timers.advanceTimersByTimeAsync(10)
543+
expect(runOrder).toEqual(['mock2', 'mock3'])
544+
545+
// Move forward to t=100
546+
await timers.advanceTimersByTimeAsync(40)
547+
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1'])
548+
549+
// Move forward to t=200
550+
await timers.advanceTimersByTimeAsync(100)
551+
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4'])
552+
553+
// Move forward to t=400
554+
await timers.advanceTimersByTimeAsync(200)
555+
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4', 'mock4'])
556+
})
557+
558+
it('does nothing when no timers have been scheduled', async () => {
559+
const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise }
560+
const timers = new FakeTimers({ global })
561+
timers.useFakeTimers()
562+
563+
await timers.advanceTimersByTimeAsync(100)
564+
})
565+
})
566+
388567
describe('advanceTimersToNextTimer', () => {
389568
it('runs timers in order', () => {
390569
const global = { Date: FakeDate, clearTimeout, process, setTimeout }
@@ -487,6 +666,108 @@ describe('FakeTimers', () => {
487666
})
488667
})
489668

669+
describe('advanceTimersToNextTimerAsync', () => {
670+
it('runs timers in order', async () => {
671+
const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise }
672+
const timers = new FakeTimers({ global })
673+
timers.useFakeTimers()
674+
675+
const runOrder: Array<string> = []
676+
const mock1 = vi.fn(() => runOrder.push('mock1'))
677+
const mock2 = vi.fn(() => runOrder.push('mock2'))
678+
const mock3 = vi.fn(() => runOrder.push('mock3'))
679+
const mock4 = vi.fn(() => runOrder.push('mock4'))
680+
681+
global.setTimeout(mock1, 100)
682+
global.setTimeout(mock2, 0)
683+
global.setTimeout(mock3, 0)
684+
global.setInterval(() => {
685+
mock4()
686+
}, 200)
687+
688+
await timers.advanceTimersToNextTimer()
689+
// Move forward to t=0
690+
expect(runOrder).toEqual(['mock2', 'mock3'])
691+
692+
await timers.advanceTimersToNextTimer()
693+
// Move forward to t=100
694+
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1'])
695+
696+
await timers.advanceTimersToNextTimer()
697+
// Move forward to t=200
698+
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4'])
699+
700+
await timers.advanceTimersToNextTimer()
701+
// Move forward to t=400
702+
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4', 'mock4'])
703+
})
704+
705+
it('run correct amount of steps', async () => {
706+
const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise }
707+
const timers = new FakeTimers({ global })
708+
timers.useFakeTimers()
709+
710+
const runOrder: Array<string> = []
711+
const mock1 = vi.fn(() => runOrder.push('mock1'))
712+
const mock2 = vi.fn(() => runOrder.push('mock2'))
713+
const mock3 = vi.fn(() => runOrder.push('mock3'))
714+
const mock4 = vi.fn(() => runOrder.push('mock4'))
715+
716+
global.setTimeout(mock1, 100)
717+
global.setTimeout(mock2, 0)
718+
global.setTimeout(mock3, 0)
719+
global.setInterval(() => {
720+
mock4()
721+
}, 200)
722+
723+
// Move forward to t=100
724+
await timers.advanceTimersToNextTimer(2)
725+
expect(runOrder).toEqual(['mock2', 'mock3', 'mock1'])
726+
727+
// Move forward to t=600
728+
await timers.advanceTimersToNextTimer(3)
729+
expect(runOrder).toEqual([
730+
'mock2',
731+
'mock3',
732+
'mock1',
733+
'mock4',
734+
'mock4',
735+
'mock4',
736+
])
737+
})
738+
739+
it('setTimeout inside setTimeout', async () => {
740+
const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise }
741+
const timers = new FakeTimers({ global })
742+
timers.useFakeTimers()
743+
744+
const runOrder: Array<string> = []
745+
const mock1 = vi.fn(() => runOrder.push('mock1'))
746+
const mock2 = vi.fn(() => runOrder.push('mock2'))
747+
const mock3 = vi.fn(() => runOrder.push('mock3'))
748+
const mock4 = vi.fn(() => runOrder.push('mock4'))
749+
750+
global.setTimeout(mock1, 0)
751+
global.setTimeout(() => {
752+
mock2()
753+
global.setTimeout(mock3, 50)
754+
}, 25)
755+
global.setTimeout(mock4, 100)
756+
757+
// Move forward to t=75
758+
await timers.advanceTimersToNextTimer(3)
759+
expect(runOrder).toEqual(['mock1', 'mock2', 'mock3'])
760+
})
761+
762+
it('does nothing when no timers have been scheduled', async () => {
763+
const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise }
764+
const timers = new FakeTimers({ global })
765+
timers.useFakeTimers()
766+
767+
await timers.advanceTimersToNextTimer()
768+
})
769+
})
770+
490771
describe('reset', () => {
491772
it('resets all pending setTimeouts', () => {
492773
const global = { Date: FakeDate, clearTimeout, process, setTimeout }
@@ -639,6 +920,113 @@ describe('FakeTimers', () => {
639920
})
640921
})
641922

923+
describe('runOnlyPendingTimersAsync', () => {
924+
it('runs all existing timers', async () => {
925+
const global = {
926+
Date: FakeDate,
927+
clearTimeout,
928+
process,
929+
setTimeout,
930+
Promise,
931+
}
932+
933+
const timers = new FakeTimers({ global })
934+
timers.useFakeTimers()
935+
936+
const spies = [vi.fn(), vi.fn()]
937+
global.setTimeout(spies[0], 10)
938+
global.setTimeout(spies[1], 50)
939+
940+
await timers.runOnlyPendingTimersAsync()
941+
942+
expect(spies[0]).toBeCalled()
943+
expect(spies[1]).toBeCalled()
944+
})
945+
946+
it('runs all timers in order', async () => {
947+
const global = {
948+
Date: FakeDate,
949+
clearTimeout,
950+
process,
951+
setImmediate,
952+
setTimeout,
953+
Promise,
954+
}
955+
956+
const timers = new FakeTimers({ global })
957+
timers.useFakeTimers()
958+
959+
const runOrder = []
960+
961+
global.setTimeout(function cb() {
962+
runOrder.push('mock1')
963+
global.setTimeout(cb, 100)
964+
}, 100)
965+
966+
global.setTimeout(function cb() {
967+
runOrder.push('mock2')
968+
global.setTimeout(cb, 50)
969+
}, 0)
970+
971+
global.setInterval(() => {
972+
runOrder.push('mock3')
973+
}, 200)
974+
975+
global.setImmediate(() => {
976+
runOrder.push('mock4')
977+
})
978+
979+
global.setImmediate(function cb() {
980+
runOrder.push('mock5')
981+
global.setTimeout(cb, 400)
982+
})
983+
984+
await timers.runOnlyPendingTimersAsync()
985+
const firsRunOrder = [
986+
'mock4',
987+
'mock5',
988+
'mock2',
989+
'mock2',
990+
'mock1',
991+
'mock2',
992+
'mock2',
993+
'mock3',
994+
'mock1',
995+
'mock2',
996+
]
997+
998+
expect(runOrder).toEqual(firsRunOrder)
999+
1000+
await timers.runOnlyPendingTimersAsync()
1001+
expect(runOrder).toEqual([
1002+
...firsRunOrder,
1003+
'mock2',
1004+
'mock1',
1005+
'mock2',
1006+
'mock2',
1007+
'mock3',
1008+
'mock5',
1009+
'mock1',
1010+
'mock2',
1011+
])
1012+
})
1013+
1014+
it('does not run timers that were cleared in another timer', async () => {
1015+
const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise }
1016+
const timers = new FakeTimers({ global })
1017+
timers.useFakeTimers()
1018+
1019+
const fn = vi.fn()
1020+
const timer = global.setTimeout(fn, 10)
1021+
global.setTimeout(() => {
1022+
global.clearTimeout(timer)
1023+
}, 0)
1024+
1025+
await timers.runOnlyPendingTimersAsync()
1026+
expect(fn).not.toBeCalled()
1027+
})
1028+
})
1029+
6421030
describe('useRealTimers', () => {
6431031
it('resets native timer APIs', () => {
6441032
const nativeSetTimeout = vi.fn()

0 commit comments

Comments
 (0)
Please sign in to comment.