skip to content

Testing a Component Tree

Mounting a subtree in a test host, settling its update queue, supplying what it needs from above, and driving it through the events its bindings listen on. Flaky component tests start here.

on this pageshow

explore

questions

22

In a component test, what does replacing real time with a fake clock make testable that a real delay cannot?

level: juniorimportance: must knowfreq 58%

answer

  1. time is an injected dependency
  2. waiting becomes an explicit move
  3. delay windows reached without delay
  4. advance the clock, then settle
  5. restore it in teardown

basics

~20 s

A fake clock turns waiting into an explicit step: the test moves time forward itself, so a debounce window, a poll interval, a timeout branch or a retry delay is reached instantly and identically on every run.

solid answer

~50 s

Timers, repeating intervals and animation-frame callbacks come from the host, and a test that waits on them pays real wall-clock time and inherits real jitter. Installing a fake clock swaps those host entry points for a queue the test drives: advancing by a stated amount fires exactly the callbacks whose due time falls inside that window, in scheduled order, with nothing else moving. That makes time-shaped branches cheap and repeatable - the pause a debounced field waits for, the second tick of a poll, the branch a request takes when it passes its own deadline, the gap between retries, the label a relative timestamp renders. Two limits matter: advancing the clock only runs the due callbacks, so committed output still needs the framework's own settle step, and the clock is shared process state, so it has to be restored when the test ends.

go deeper

for a junior

Recall that a test can own the clock: instead of waiting out a delay, it moves time forward by a stated amount and the callbacks due in that window fire.

for a middle

Be able to name what a fake clock replaces - one-shot timers, intervals, frame and idle callbacks, the current-time reading - and to say that firing a callback is not the same as the framework rendering its result.

for a senior

Show the operational half: install and restore in shared hooks, advance in steps that match the product's real windows, and treat callbacks left pending at teardown as a defect rather than noise.

for a principal

Frame the tradeoff. A controlled clock buys determinism for delay-shaped logic and says nothing about the durations users actually feel, so decide deliberately where in the portfolio real timing is measured instead.

## Time is a dependency, not a background fact A component test already controls its inputs: the props it mounts with, the values its ancestors provide, the events it dispatches into bindings. Time is an input too, and it is the one teams habitually leave uncontrolled. A component that debounces a field, polls for a status, gives a request a deadline, spaces retries apart, or renders a relative timestamp has behaviour whose **trigger is the passage of time**. Left real, that behaviour costs wall-clock seconds to reach and arrives with jitter: a loaded machine can miss a window by enough to change which branch runs, which is how a correct component acquires an intermittently failing test. Installing a fake clock makes time an argument the test supplies. The host's time-driven entry points are replaced by a queue the test owns, and advancing that queue by a stated amount fires exactly the callbacks whose due time falls inside it, in scheduled order, while nothing else moves. ## What installing a clock takes over - **one-shot timers** - the primitive a debounce, a deadline and a retry delay are built on; - **repeating intervals** - the primitive a poll is built on; - **animation-frame callbacks** - frame-aligned work, and often a framework's own batching; - **idle callbacks** - low-priority work a framework or a component defers; - **the current-time reading** - what a component renders a relative timestamp, a countdown or an expiry from. Most clocks let a test choose which of these to take over. That choice matters, because the more entry points you freeze, the more of the framework's own machinery you may have frozen with them. ## What becomes testable | Behaviour | Under real time | Under a controlled clock | |---|---|---| | A debounce window | wait out the real pause in every test | move past the window and assert the single resulting action | | A poll interval | the second tick arrives seconds later | advance one interval at a time and count what was requested | | A deadline branch | reachable only by making the work genuinely slow | advance past the deadline and assert the timeout output | | A retry delay | the suite pays the sum of every backoff | advance each gap and assert the attempt sequence | | A relative timestamp | the rendered text changes between runs | pin the reading so the text is a constant | The pattern in that column is the point: a delay stops being something the test endures and becomes something the test **states**. A window of two hundred milliseconds and a backoff of thirty seconds cost the same - one advance each. ## What advancing the clock does not do - It does not drain the framework's pending update work. A timer callback writes state; turning that write into committed output is a separate settle step. - It does not release work that no timer gates. Continuation queues are drained on every yield and never consult the clock, so pending async work resolves on its own schedule. - It does not speed up code that scheduled nothing. Synchronous work that is simply slow is unaffected. - It does not decide what a pending request returns. A fake clock controls *when* something happens, never *what* it produces. - It does not change host-computed facts. Element geometry, style matching and focus order come from the host, and no clock position moves them. ## The discipline that keeps it honest 1. **Install per test, in shared setup.** A clock installed ad hoc in a test body is a clock that leaks when the body fails early. 2. **Advance in named steps that match real windows.** Advancing by the product's actual debounce or interval documents the behaviour; advancing by an arbitrary large jump hides which window mattered. 3. **Settle after advancing.** The two queues are separate, and asserting between them is the classic flake. 4. **Restore in teardown.** Shared host state that one test replaced must be given back whether the test passed or failed. ## Honest limits A clock-driven suite proves the *logic* of a delay, not the duration a user feels. It says a debounce fires once after its window, never that the window is well chosen; it says a deadline branch renders, never that the deadline is reachable on a slow connection. Those are judgments for a different layer of the portfolio. What the fake clock buys at this layer is the ability to test the branch at all - and to test it in milliseconds, the same way, on every machine that runs the suite.

  • What does a controlled clock not help with when a component waits on work that no timer gates?
    Nothing at all - that work is released by its own resolution, not by time. Advancing the clock fires no callback for it, so the test still has to let queued continuations run and then settle the framework before output appears. A clock only helps where a delay is genuinely the gate.
  • Why is a component's reading of the current time worth pinning as well as its timers?
    Because output derived from the current time - a relative timestamp, a countdown, an expiry badge - changes by itself between runs, so no stable assertion can be written against it. Pinning the reading makes that output a constant, and advancing the clock then lets the test check how it changes.
  • When is waiting real time still defensible in a component test?
    Rarely, and never for logic you can reach by moving the clock. It is defensible when the behaviour belongs to the host and is not exposed through an entry point the test can replace; even then the test should wait on a readiness condition rather than a fixed duration, so it is not encoding a guess about speed.

saying these in an interview costs you the question

  • Treats a fixed real sleep as equivalent to advancing a controlled clock
  • Thinks advancing the clock also drains the framework's pending update work
  • Expects a faked clock to release async work that no timer was gating
  • Leaves the clock installed after the test, so later tests inherit frozen time
  • Believes faking time speeds up synchronous code that scheduled nothing
open as a page

A component test fails to mount because the tree reads values from ancestors it does not have. What must the test supply?

level: juniorimportance: must knowfreq 60%

basics

~20 s

Everything the tree reads from above it: the ancestor-provided values it expects (theme, locale, session), a router, any external store it subscribes to, and a data client. The test wraps the component in those providers before mounting.

open as a page

In a component test, why does assigning a bound text control's value property leave the component's state unchanged?

level: juniorimportance: must knowfreq 68%

basics

~20 s

A binding is a listener the framework registered on that control for one host event, so writing the element's value property only changes the control and dispatches nothing. The listener never runs, and state keeps the old value.

open as a page

In a component test, what does mounting a tree into a test host mean, and what does the root handle it returns let you do?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Mounting attaches a component tree to a host the test owns — usually a container the test created in a simulated document — and returns a root handle. Through it the test queries rendered output, feeds new inputs into the same root, and tears the tree down.

open as a page

With time faked in a component test, why must the clock be advanced and the framework's update queue settled separately?

level: middleimportance: must knowfreq 56%

basics

~20 s

They are two different queues with two different triggers. Advancing the clock fires timer callbacks, and those callbacks only write state; turning a write into committed output is the framework's settle step. Advance, settle, then assert - once per link.

open as a page

How do you wire a component test so the tree renders as if the user arrived at a particular URL, and what can it then assert?

level: middleimportance: must knowfreq 52%

basics

~20 s

Wrap the tree in a router whose history is an in-memory list rather than the host's address bar, seeded with the URL under test. The test can then assert which route's content rendered, and where a redirect left the location.

open as a page

Why can a component test that drives a bound field with the commit event pass while the per-keystroke behaviour it claims to cover is broken?

level: middleimportance: must knowfreq 58%

basics

~20 s

The commit event satisfies a lazy binding and a live one alike, so the assertion cannot tell them apart. Only dispatching the per-keystroke event on its own proves state moves while the user is still typing.

open as a page

Why can an assertion made immediately after a state write in a component test still read the previous rendered output?

level: middleimportance: must knowfreq 78%

basics

~20 s

A write only queues work. Frameworks batch updates and apply them later, so the host still holds the previous render until the framework's settle step has run — the test must await or invoke that step before asserting.

open as a page

In a component test, at which point in the advance-and-settle sequence do you assert a component's pending output?

level: middleimportance: should knowfreq 47%

basics

~20 s

Immediately after the settle that commits the write starting the async work, and before the clock advances or the work is released. The pending shape exists only in that window; an end-state assertion cannot prove it appeared.

open as a page

Why do teams put component-test provider wiring behind one shared mount helper, and what must each test still control?

level: middleimportance: should knowfreq 55%

basics

~20 s

Because the provider stack is near-identical in every test, and hand-wiring it duplicates the application's composition in hundreds of files. Each test still controls its own values: the starting URL, seeded store state, cached entries, collaborator overrides.

open as a page

After driving a bound field in a component test, when is reading the element's own value back a circular assertion?

level: middleimportance: should knowfreq 49%

basics

~20 s

Whenever the test wrote that value itself. The text stays on the element unless a render overwrites it, so the read-back passes even if the binding never ran. Assert on output only state can produce.

open as a page

How do you test a reusable stateful logic unit that only runs inside a component's reactive scope?

level: middleimportance: should knowfreq 54%

basics

~20 s

Give it a scope. Either mount a throwaway host component that calls the unit and exposes its result, or use a harness that opens a reactive scope directly. Updates it produces are settled exactly like a render, and the scope is disposed at the end.

open as a page

A component suite's later tests hang only when one earlier test runs first; how can a faked clock cause that?

level: seniorimportance: should knowfreq 42%

basics

~20 s

The clock is process-wide state. A test that installs it and never restores it leaves later tests with time frozen, so their debounce, poll and timeout waits never fire - and its leftover callbacks can fire into a torn-down tree.

open as a page

Why can installing a fake clock stop a framework's update scheduling from advancing while a microtask-based flush keeps working?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Because frameworks batch commit work onto different host entry points. A flush drained on the continuation queue is not time-driven and keeps running; work parked on a timer, idle callback or animation frame advances only when the frozen clock moves.

open as a page

Your mount helper builds a fresh provider per test, yet a value one test wrote is still visible in the next. Where is the leak?

level: seniorimportance: should knowfreq 44%

basics

~10 s

In state created once when a module first loads — a store, cache or client built at module scope. The provider is new each test, but it hands the tree the same long-lived instance.

open as a page

Why seed a store or prewarm a data cache before mounting, instead of driving the tree through the steps that would produce that state?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Because the state is the case's precondition, not its subject. Seeding puts the tree straight into the situation under test, keeps the setup readable and deterministic, and stops every test from depending on the correctness of an unrelated flow.

open as a page

In a component test of a parent, how do you trigger an event a child component emits, and what should you assert?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Go through the framework's own output channel: emit from a stub child, or drive the real child's control so its binding emits. Then assert on what the parent rendered from the payload, not on the emitter being called.

open as a page

A bound control's value cannot be written from a test - a file picker, for instance. How do you still cover the binding?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Use the platform's own supply route where one exists, then dispatch the event the binding listens on. Otherwise call the component's payload handler directly with a realistic payload, and say in the test what is left uncovered.

open as a page

Why can a single settle step leave a component test asserting on an intermediate render, and what loop fixes it?

level: seniorimportance: should knowfreq 56%

basics

~20 s

Applying one batch can create another: an effect that runs after commit writes state and queues a second update. So one settle can return with work still pending, and a harness typically loops — settle, check for pending work, settle again — until the tree is quiescent.

open as a page

What can a component test not prove when its host is a simulated document rather than a real browser engine?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Anything downstream of layout and paint. A simulated document models the tree and its APIs but lays nothing out, so measured sizes and positions, overflow, styling-driven visibility, scrolling, hit-testing and real navigation are stubbed or absent — and stubs answer, rather than fail.

open as a page

As the owner of a large component-test suite, how do you decide how much of the application's real provider stack the default mount wires?

level: principalimportance: nice to knowfreq 32%

basics

~10 s

Per collaborator, by cost and evidence: run in-process deterministic ones for real, stand in for anything crossing a boundary or a clock. Publish the default, make deviations explicit, revise it when defects escape.

open as a page

As a lead, how would you decide between rendering one level deep and mounting a real subtree in component tests?

level: principalimportance: nice to knowfreq 40%

basics

~20 s

Make mounting the real subtree the default and treat one-level rendering as a named exception. Rendering one level deep never instantiates the children, so nothing they do on mount happens and no composed output exists — the speed is real, the coverage loss is larger.

open as a page