skip to content

In a Cypress component test, how do you fast-forward a toast's auto-dismiss timer?

level: seniorimportance: nice to knowfreq 24%

answer

  1. Real waiting is a tax on every run
  2. Fake the timers, then move them
  3. Install before anything can schedule
  4. The fake clock starts at zero
  5. cy.tick() needs cy.clock() first

basics

~20 s

Call cy.clock() before cy.mount(), so the fake timers are installed before the component can schedule anything, then move time with cy.tick(5000) and assert the toast is gone. Cypress restores the real clock automatically between tests.

solid answer

~40 s

`cy.clock()` replaces `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval` and `Date` with controllable fakes, and `cy.tick(ms)` moves that fake clock forward, firing every timer inside the window synchronously. Order matters: install the clock **before** `cy.mount()`, because a dismissal the component schedules during its first render would otherwise have captured the real `setTimeout`, and no tick will ever fire it. So: `cy.clock()`, then `cy.mount(<Toast duration={5000} onDismiss={cy.spy().as('onDismiss')} />)`, then `cy.tick(5000)`, then `cy.get('@onDismiss').should('have.been.calledOnce')` and `cy.get('[data-cy=toast]').should('not.exist')`. Two things to remember: the fake clock starts at the Unix epoch, so any `new Date()` inside the component reads 1 January 1970 unless you seed it — `cy.clock(Date.UTC(2026, 8, 1))`. And Cypress restores the real timers between tests, so no teardown is needed.

go deeper

for a junior

Know the pair: cy.clock() installs fake timers and cy.tick() moves them. Being able to say why a test should not really wait five seconds is enough here.

for a middle

Explain what cy.clock() actually replaces, why cy.tick() only fires timers scheduled after it was installed, and how the clock object is reached.

for a senior

Show the ordering judgement and the epoch consequence: you should be able to look at a failing timed test and say whether the clock or the mount came first.

for a principal

Decide where faked time is allowed in the suite at all. It buys minutes per run and costs realism, and a component that reads the clock in two places will eventually disagree with itself.

A toast that dismisses itself after five seconds is trivially testable and ruinously slow to test honestly. Twenty such assertions is a hundred seconds of a suite doing nothing. Cypress's answer is to **fake the clock** rather than wait on it: replace the browser's timer functions with controllable stand-ins, then move time forward by hand. ## What `cy.clock()` replaces By default `cy.clock()` overrides five globals on the top window: - `setTimeout` and `clearTimeout` - `setInterval` and `clearInterval` - `Date` Two limits are worth stating up front. The fake applies to the **top window only** — it does not reach into an embedded iframe — and it only affects timers scheduled *after* it is installed. Anything already scheduled against the real `setTimeout` keeps running on real time, invisible to `cy.tick()`. ## Why the clock goes in before the mount This is the ordering trap, and it is specific to component tests. Mounting a component renders it but does not touch the page or the window object, which means installing the clock first is both safe and correct: ```js cy.clock() cy.mount(<Toast duration={5000} onDismiss={cy.spy().as('onDismiss')} />) cy.tick(5000) cy.get('@onDismiss').should('have.been.calledOnce') cy.get('[data-cy=toast]').should('not.exist') ``` Reverse those two lines and the toast schedules its dismissal in its first render, against the *real* `setTimeout`. `cy.tick(5000)` then advances a clock that owns no timers, the assertion fails, and the failure looks like a broken component rather than a mis-ordered test. `cy.tick()` also requires that `cy.clock()` has already run — calling it alone is an error, not a no-op. ## The tick-and-assert loop 1. `cy.clock()` — install the fakes, optionally seeded with a timestamp. 2. `cy.mount(...)` — render, with a spy on whatever callback the timer will fire. 3. `cy.tick(ms)` — advance. Every timer whose deadline falls inside that window fires synchronously. 4. Assert — on the spy alias, on the DOM, or on both. Ticking is cumulative, so a polling data grid on a 30-second refresh can be walked forward three intervals with three `cy.tick(30000)` calls, asserting between each. Advancing in one large jump fires the same timers, but you lose the ability to check the state in between. ## The epoch trap `cy.clock()` with no argument starts the clock at Unix time zero. Every `new Date()` inside the component then reads **1 January 1970**, which is invisible until a component renders a relative timestamp — a data grid's "edited 2 minutes ago" column suddenly reports half a century. Three ways out, in order of preference: - **Seed the clock** with a real timestamp: `cy.clock(Date.UTC(2026, 8, 1))`, and let fixtures use dates near it. - **Fake only the timers**, leaving the calendar alone: `cy.clock(null, ['setTimeout', 'clearTimeout'])`. Note that `Date` must be listed explicitly for the current date-time to be overridden at all. - **Move the system time later** without firing anything: `cy.clock().invoke('setSystemTime', someTimestamp)`, which changes the calendar but leaves pending timers where they are. ## Narrowing and restoring `cy.clock()` yields a clock object with `tick(ms)`, `setSystemTime(now)` and `restore()`, reachable through `.then()` or `.invoke()`: | Goal | Write | | --- | --- | | advance time and fire timers | `cy.tick(5000)` | | jump the calendar, fire nothing | `cy.clock().invoke('setSystemTime', ts)` | | hand control back to real timers | `cy.clock().invoke('restore')` | | fake a subset of the globals | `cy.clock(null, ['setTimeout', 'clearTimeout'])` | Restoring mid-test has a sharp edge: timers registered while the fake clock was installed are **discarded**, not resumed. A component that started polling under the fake clock will not poll again unless it re-registers. In practice you rarely need to restore at all, because Cypress does it between tests. ## What changed in Cypress 16 Cypress 16 upgraded its bundled fake-timers library, and the faked `performance` object is now less of a stub: `performance.mark()` and `performance.measure()` actually work while the clock is installed, where previously they were no-ops returning `undefined`, and `performance.timeOrigin` reports a faked value. A component that instruments itself with the User Timing API — an animation-heavy toast or a virtualised grid, for instance — no longer breaks merely because a test faked time.

  • The grid's 'edited 2 minutes ago' column now reads decades. Why?
    `cy.clock()` fakes `Date` as well as the timer functions, and with no argument the fake clock starts at the Unix epoch, so `new Date()` inside the component reads 1 January 1970 while the fixture's timestamp is a real 2026 date. Seed the clock instead — `cy.clock(Date.UTC(2026, 8, 1))` — or fake only the timers with `cy.clock(null, ['setTimeout', 'clearTimeout'])`.
  • How do you hand control back to the real clock partway through a test?
    Take the yielded clock object and restore it: `cy.clock().invoke('restore')`, or `cy.clock().then((clock) => clock.restore())`. Be aware that timers registered while the fake clock was installed are discarded rather than resumed, so a component that started polling before the restore will not poll again unless it re-registers its own interval.

saying these in an interview costs you the question

  • Calling cy.tick() without ever calling cy.clock()
  • Mounting first, then wondering why cy.tick() fires nothing
  • Adding a real five-second wait instead of faking time
  • Forgetting the fake clock also freezes Date at the epoch
  • Expecting cy.clock() to reach timers inside an iframe