Skip to content

Commit ed7e3ad

Browse files
authored
fix: override fake timers when useFakeTimers is called multiple times (#8504)
1 parent d3afa60 commit ed7e3ad

File tree

2 files changed

+33
-28
lines changed

2 files changed

+33
-28
lines changed

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

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ export class FakeTimers {
2020
// | _fakingTime | _fakingDate |
2121
// +-------------+-------------+
2222
// | false | falsy | initial
23-
// | false | truthy | vi.setSystemTime called first (for mocking only Date without fake timers)
23+
// | false | truthy | vi.setSystemTime called first (for mocking only Date without fake timers)
2424
// | true | falsy | vi.useFakeTimers called first
25-
// | true | truthy | unreachable
25+
// | true | truthy | unreachable
2626
private _fakingTime: boolean
2727
private _fakingDate: Date | null
2828
private _fakeTimers: FakeTimerWithContext
@@ -145,34 +145,36 @@ export class FakeTimers {
145145
}
146146

147147
useFakeTimers(): void {
148+
const fakeDate = this._fakingDate || Date.now()
148149
if (this._fakingDate) {
149-
throw new Error(
150-
'"setSystemTime" was called already and date was mocked. Reset timers using `vi.useRealTimers()` if you want to use fake timers again.',
151-
)
150+
resetDate()
151+
this._fakingDate = null
152152
}
153153

154-
if (!this._fakingTime) {
155-
const toFake = Object.keys(this._fakeTimers.timers)
156-
// Do not mock timers internally used by node by default. It can still be mocked through userConfig.
157-
.filter(
158-
timer => timer !== 'nextTick' && timer !== 'queueMicrotask',
159-
) as (keyof FakeTimerWithContext['timers'])[]
160-
161-
if (this._userConfig?.toFake?.includes('nextTick') && isChildProcess()) {
162-
throw new Error(
163-
'process.nextTick cannot be mocked inside child_process',
164-
)
165-
}
154+
if (this._fakingTime) {
155+
this._clock.uninstall()
156+
}
166157

167-
this._clock = this._fakeTimers.install({
168-
now: Date.now(),
169-
...this._userConfig,
170-
toFake: this._userConfig?.toFake || toFake,
171-
ignoreMissingTimers: true,
172-
})
158+
const toFake = Object.keys(this._fakeTimers.timers)
159+
// Do not mock timers internally used by node by default. It can still be mocked through userConfig.
160+
.filter(
161+
timer => timer !== 'nextTick' && timer !== 'queueMicrotask',
162+
) as (keyof FakeTimerWithContext['timers'])[]
173163

174-
this._fakingTime = true
164+
if (this._userConfig?.toFake?.includes('nextTick') && isChildProcess()) {
165+
throw new Error(
166+
'process.nextTick cannot be mocked inside child_process',
167+
)
175168
}
169+
170+
this._clock = this._fakeTimers.install({
171+
now: fakeDate,
172+
...this._userConfig,
173+
toFake: this._userConfig?.toFake || toFake,
174+
ignoreMissingTimers: true,
175+
})
176+
177+
this._fakingTime = true
176178
}
177179

178180
reset(): void {
@@ -221,7 +223,8 @@ export class FakeTimers {
221223
private _checkFakeTimers() {
222224
if (!this._fakingTime) {
223225
throw new Error(
224-
'Timers are not mocked. Try calling "vi.useFakeTimers()" first.',
226+
'A function to advance timers was called but the timers APIs are not mocked. '
227+
+ 'Call `vi.useFakeTimers()` in the test file first.',
225228
)
226229
}
227230

test/core/test/fixtures/timers.suite.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ describe('FakeTimers', () => {
278278
},
279279
global,
280280
})
281-
expect(() => timers.runAllTimers()).toThrow(/Timers are not mocked/)
281+
expect(() => timers.runAllTimers()).toThrow(/A function to advance timers was called but the timers APIs are not mocked/)
282282
})
283283

284284
it('does nothing when no timers have been scheduled', () => {
@@ -422,7 +422,7 @@ describe('FakeTimers', () => {
422422
},
423423
global,
424424
})
425-
await expect(timers.runAllTimersAsync()).rejects.toThrow(/Timers are not mocked/)
425+
await expect(timers.runAllTimersAsync()).rejects.toThrow(/A function to advance timers was called but the timers APIs are not mocked/)
426426
})
427427

428428
it('only runs a setTimeout callback once (ever)', async () => {
@@ -1494,7 +1494,9 @@ describe('FakeTimers', () => {
14941494

14951495
expect(Date.now()).toBe(timeStrMs)
14961496

1497-
expect(() => timers.useFakeTimers()).toThrowError(/date was mocked/)
1497+
expect(() => timers.useFakeTimers()).not.toThrowError()
1498+
1499+
expect(Date.now()).toBe(timeStrMs)
14981500

14991501
// Some test
15001502

0 commit comments

Comments
 (0)