Skip to content

Commit 21cfb47

Browse files
committed
test(memoization): stabilize timestamp-refresh test under parallel load
The fake-timers + `advanceTimersByTimeAsync` combination was flaky when the file ran concurrently with other suites — microtask scheduling around `vi.useFakeTimers` is not deterministic under Vitest's worker pool. Replace with a `Date.now` spy plus a manual promise resolver. We directly control virtual time (`fakeTime += 500`) and the fn's completion point (`resolveFn!(2)`), so the regression assertion — that the cache entry's timestamp gets refreshed on resolve, not just on initial set — holds without any timer coordination.
1 parent 4523738 commit 21cfb47

1 file changed

Lines changed: 23 additions & 11 deletions

File tree

test/unit/memoization.test.mts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -280,29 +280,41 @@ describe('memoization', () => {
280280
// TTL window would re-fetch despite the cache having just landed a
281281
// fresh value. The fix sets `entry.timestamp = Date.now()` in the
282282
// resolve handler so the cached hit window starts when the result
283-
// is available. Uses fake timers so the assertion isn't sensitive
284-
// to real-world microtask jitter under parallel test load.
285-
vi.useFakeTimers()
283+
// is available. Uses a stubbed `Date.now` (not fake timers) so we
284+
// don't depend on Vitest's timer/microtask interleaving under
285+
// parallel worker load.
286+
const realNow = Date.now
287+
let fakeTime = 1_000_000
288+
const nowSpy = vi.spyOn(Date, 'now').mockImplementation(() => fakeTime)
286289
try {
287290
let callCount = 0
291+
let resolveFn: ((v: number) => void) | undefined
288292
const slowFn = async (n: number) => {
289293
callCount++
290-
await new Promise(r => setTimeout(r, 500))
291-
return n * 2
294+
const result = await new Promise<number>(r => {
295+
resolveFn = r
296+
})
297+
return n * result
292298
}
293299
const memoized = memoizeAsync(slowFn, { ttl: 200 })
300+
// Start call at t=1_000_000.
294301
const p1 = memoized(5)
295-
await vi.advanceTimersByTimeAsync(500)
302+
// Advance past TTL (500 > 200) BEFORE resolving the fn.
303+
fakeTime += 500
304+
resolveFn!(2)
296305
expect(await p1).toBe(10)
297306
expect(callCount).toBe(1)
298-
// Without advancing time further, the second call should hit
299-
// because the resolve handler set timestamp = now(500).
300-
// (Pre-fix: timestamp was still 0 — 500ms old → expired → new
301-
// call would fire. Post-fix: 0ms old → hit.)
307+
// Without advancing further, the second call should hit because
308+
// the resolve handler reset the entry timestamp to t=1_000_500.
309+
// (Pre-fix: timestamp was 1_000_000 — age 500 > ttl 200 →
310+
// expired → second call would fire.)
302311
expect(await memoized(5)).toBe(10)
303312
expect(callCount).toBe(1)
304313
} finally {
305-
vi.useRealTimers()
314+
nowSpy.mockRestore()
315+
// Belt-and-suspenders: restore original even if spy cleanup
316+
// leaves a bound impl in place.
317+
Date.now = realNow
306318
}
307319
})
308320

0 commit comments

Comments
 (0)