Why do tests about time go wrong?
isOpenNow() reads the clock. A test that calls it gives one answer at 10 in the morning and another at 8 in the evening, without a line of code changing. A test that sometimes passes and sometimes fails is called flaky, and flaky tests are worse than missing ones: people learn to rerun them until they go green, and then a real failure gets rerun too.
Delays have the opposite problem. flashNotice() clears its text after 3 seconds. A test that really waited would add 3 seconds to every run, and a suite with a few of those soon takes minutes. Code with a one-hour reminder can't be tested by waiting at all.
Both problems have the same fix. In a test you take control of the clock, so time is whatever the test says and moves only when the test moves it.
What do fake timers replace?
vi.useFakeTimers() swaps the real setTimeout, setInterval, their clear partners and Date for versions the test controls. A timer set while fake timers are on goes into a queue and doesn't run until you advance the clock. new Date() returns the fake current time. Jest has the same idea as jest.useFakeTimers().
Turn them on in beforeEach and off with vi.useRealTimers() in afterEach. Real Vitest leaves fake timers on until you switch them off, so a forgotten useRealTimers() leaks into later tests and can make unrelated tests hang. The practice runner switches back after every test on its own, which is kinder, and differs from the real tool. Write the afterEach anyway so the file works on your own machine.
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});How do you test opening hours with setSystemTime?
vi.setSystemTime(date) sets what the clock reads. Inside the test, everything that asks for the time gets that date. Write the date with a Z on the end, such as '2026-03-18T09:00:00Z', so it means the same moment on every computer. Without the Z, the string is read in the local time zone, and a test written in London fails on a server in Virginia.
Choose dates on purpose and know their weekdays. 18 March 2026 is a Wednesday, 15 March is a Sunday. Put the weekday in the test name, because nobody remembers what day 2026-03-15 was. Then test the boundaries the rules create: 8:00 opens, 7:59 doesn't; 16:59 is open, 17:00 is closed. A broken copy that uses <= passes every test except the one at exactly 17:00.
test('closed at exactly 17:00', () => {
vi.setSystemTime(new Date('2026-03-18T17:00:00Z'));
expect(isOpenNow()).toBe(false);
});How do you test a delay without waiting?
Run the code that starts the timer, then move the clock forward with vi.advanceTimersByTime(ms). Every timer due in that window runs straight away, in order, and the test carries on. A 3 second delay takes no real time.
Check both sides of the delay. Advance 2999 milliseconds and expect the notice to still be there, then advance 1 more and expect it gone. Checking only the end, after 5000 milliseconds say, passes for a notice that vanishes after 300 milliseconds, which is the broken copy Check my work tries. The point of the delay is that the message stays readable, so the test should prove it stays.
vi.runAllTimers() runs everything in the queue, however far ahead. It is handy when the exact time doesn't matter. Avoid it with setInterval, which schedules itself again every time it runs; the runner stops after a safety limit, and real Vitest stops after 10,000 timers with an error. Use advanceTimersByTime for repeating timers.
flashNotice(notice, 'Added to basket');
vi.advanceTimersByTime(2999);
expect(notice.text).toBe('Added to basket');
vi.advanceTimersByTime(1);
expect(notice.text).toBe('');

Is it better to pass the time in?
Often, yes. A function written as isOpenAt(date) needs no fake timers at all: the test passes a date and checks the answer. The site calls isOpenAt(new Date()). Many teams write the rule as a function of its inputs and keep the one call to the real clock at the edge of the program, the same way the reminders code in the mocks lesson receives send.
Fake timers are still the right tool when you can't change the signature, when the code uses setTimeout itself, or when you test a page that reads the clock deep inside. The pickupDay(dropOff = new Date()) function in the Break It challenge does both: it takes a date when given one and falls back to the clock.
How does the practice runner's clock differ from real Vitest?
To make every run repeatable, the practice runner fixes Date at Saturday 14 March 2026, 09:00 UTC for every test, even when fake timers are off. That is why the starting test passes here: 9 on a Saturday morning is inside opening hours. In real Vitest, Date reads the real clock until you set it, so the same test would fail every evening and every Sunday.
The runner also seeds Math.random, so random numbers come out the same every run. Real tools don't, which is another source of flaky tests. If your code uses randomness, pass the random value in, or spy on Math.random with vi.spyOn(Math, 'random').mockReturnValue(0.5).
What about timers inside async code?
If the code awaits a promise and then sets a timer, advancing the clock straight away may run nothing, because the timer doesn't exist yet. await vi.advanceTimersByTimeAsync(ms) advances the clock and lets pending promises settle between timers. Use the async versions whenever the code under test mixes await and timers. The next lesson covers async tests in detail.
What do AI tools get wrong with time?
Generated tests often call new Date() in the test to build the expected value, repeating the code's own logic, so the test can't disagree with the code. Others assert on today's date written as a literal, which passes on the day the test was written and fails from then on. Both should set the clock and state the expected answer outright.
Also look for real waiting: await new Promise((r) => setTimeout(r, 3000)) inside a test is a sign fake timers were needed. And check that every useFakeTimers() has a matching useRealTimers().
- Set the clock with
setSystemTimeand aZdate - Name the weekday in the test
- Test both sides of every boundary and every delay
- Switch real timers back on in
afterEach
Field notes
- A test that reads the real clock can pass in the morning and fail at night.
vi.useFakeTimers()lets the test decide the time and when it moves.- Use
vi.setSystemTime()with a date ending inZand name its weekday. vi.advanceTimersByTime()runs a delay at once, so check 2999 and 3000 ms.
Easy to get wrong
Without a fixed clock the answer depends on when the test runs. Set the time so the test means the same thing every run.
A date string with a time and no zone is read as local time, so it is a different moment on different machines. Add Z for UTC.
Checking only the end passes if the notice disappears far too early. Check just before the deadline and just after.
Questions people ask
Do fake timers make `await` hang?
Plain promises still resolve with fake timers on. A promise that waits on a setTimeout won't resolve until you advance the clock, so use await vi.advanceTimersByTimeAsync(ms) in that case.
Can I use `setSystemTime` without fake timers?
In real Vitest, yes: with fake timers off it only changes what Date returns. Turning fake timers on as well is the usual pattern, because it also holds the clock still while the test runs.
How do I see how many timers are waiting?
vi.getTimerCount() returns the number of fake timers in the queue. It is a quick way to check that code cleared its timer, for example that cancelling a reminder leaves zero.
Should my code use UTC or local time?
Store and compare in UTC, and convert to the shop's local time when you display it. The demo uses UTC throughout to keep the rule easy to read; a real bakery would also need to handle summer time.