skip to content

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%

answer

  1. output is a sequence, not a value
  2. the transient shape needs a window
  3. assert between two settles
  4. presence then absence, both
  5. release the failure inside the test

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.

solid answer

~40 s

Asynchronous output has at least three observable shapes - pending, resolved and failed - and the pending one is destroyed by finishing the work. So the assertion belongs in the interval between two settles: settle once so the trigger's state write is committed, assert the pending branch, then advance the clock or release the pending work, settle again, and assert the resolved branch. A test that only checks the end state passes for a component that never showed a pending branch at all, and equally for one whose indicator renders forever next to the loaded content. Assert the failure branch the same way, releasing the failing work inside the sequence so the component's own handler observes the rejection rather than the runner reporting it as unhandled.

go deeper

for a junior

Remember that async output has more than one shape. Check the placeholder while the work is still pending, then check the content after it finishes - two assertions, not one.

for a middle

Explain why the pending shape only exists between two settles, and why presence followed by absence is the pair that proves a transition rather than either check alone.

for a senior

Show that you also drive the failure path in the same sequence, assert the fallback rather than the absence of content, and keep rejections observed so they cannot surface against an unrelated test.

for a principal

Make it a standard: every async surface in the suite covers pending, resolved and failed, because the waiting and failing states are what users meet on bad networks and what nobody notices regressing.

## Three shapes, one of them transient A component that starts asynchronous work renders a sequence, not a value. First it renders **pending** output - a placeholder, a skeleton, a disabled submit button, an inline progress label. Then it renders either the **resolved** output or a **failed** fallback. Two of those three shapes survive until the end of the test. The pending one does not: finishing the work erases it, and a test that finishes the work before it asserts has destroyed the only evidence that the branch exists. That is why the placement of the assertion, not the wording of it, is the skill here. The pending shape lives in the interval **between two settles**: after the framework has committed the state write that started the work, and before anything releases the work itself. ## Where each assertion goes 1. **Drive the trigger** - the event or state write that starts the work. 2. **Settle** so the framework commits the output that write produced. 3. **Assert the pending branch** - the placeholder is present, the control is disabled, the previous content is either kept or cleared, whichever the design says. 4. **Release the work** - advance the clock past the delay it is waiting on, or let its resolution through, then yield. 5. **Settle again** and **assert the resolved branch** - the content is present *and* the placeholder is gone. Step 5 carries two assertions on purpose. The presence check proves the happy path; the absence check proves the transition completed. Together they cost one extra line and close the most common gap in this area. ## What an end-state-only test cannot see | Assertion the test makes | What it proves | What it still misses | |---|---|---| | resolved content is present | the success path renders and maps its fields | whether any pending output ever appeared | | resolved content is present | the work completed | whether the placeholder was cleared | | placeholder is absent at the end | the placeholder is not permanent | whether it was ever shown, since a component that never renders it also passes | | pending output present, then absent | the whole transition, in order | nothing about the failure branch until that is driven too | The third row is the one that surprises people: an absence assertion is satisfied by a component that has no pending branch at all. Absence alone is not evidence of a transition; only presence **followed by** absence is. ## The failure branch, without a stray rejection The same interleaving covers failure, with one extra concern. When the pending work fails, something has to observe the rejection. If the failing work is released after the test has finished - or if the component's handler never ran because the test never settled again - the runner sees a rejection nobody handled, and reports it against whichever test happens to be running then. The discipline is to release the failure **inside** the sequence: advance or resolve, yield, settle, and assert the fallback output. If the component's own handler runs, the rejection is observed where it should be, and the test's assertion on the fallback is what reports a regression. - Assert the fallback's text or role, not merely that the resolved content is absent - absence also holds while the work is still pending. - Assert that any retry affordance the failure branch offers is actually rendered, since that is frequently the part that regresses. - Never leave a failing path un-released at the end of a test; a pending rejection is a time bomb for the next one. ## Where reactivity models differ How wide the pending window is depends on the runtime. A runtime that re-runs the component function on each write gives a clean two-phase sequence: the write flips a flag, the re-run renders the placeholder, and the pending shape is stable until the next write. A runtime with fine-grained tracking may update the derived flag the instant the write happens while the host commit is still queued, so the *state* says pending before the *output* does - which is another reason to assert on rendered output rather than on the flag. A compile-time runtime can commit so eagerly that the window is a single settle wide. None of that changes the rule; it changes only how obvious the rule's necessity is on a given stack. ## Why this matters more than it looks Pending output is the part of a feature users see under exactly the conditions engineers do not: a slow network, a cold cache, a large response. It is also the part most often written once and never exercised again. A suite that asserts end states has full green coverage of the screens that are easy to reach and no coverage of the seconds a real user spends waiting. Two assertions placed in the right interval, plus one driven failure, turn that into real coverage without adding a single second of wall-clock time.

  • How do you assert the failure branch without the runner reporting an unhandled rejection?
    Release the failing work inside the test's own advance-and-settle sequence, so the component's handler runs before the test ends, then assert the fallback output. A rejection that nobody observed during the test is what the runner flags - usually against an unrelated test that was running when it surfaced.
  • Which single extra assertion best guards against a placeholder that never clears?
    Checking that the placeholder is absent at the same point the resolved content is asserted present. Paired with the earlier presence check, two cheap assertions cover both directions: the indicator appeared, and the transition finished.
  • Why is asserting on a pending flag in state weaker than asserting on the pending output?
    Because the flag is the component's intent and the output is its behaviour. In some runtimes the flag is already true while the commit that would render the placeholder is still queued, so a flag assertion can pass for a component that renders no pending output at all.

saying these in an interview costs you the question

  • Asserts only the resolved output and calls the loading state covered
  • Believes asserting the resolved content also proves the placeholder disappeared
  • Treats an absence assertion alone as evidence a placeholder was shown
  • Releases all pending work before asserting, then claims the pending state is untestable
  • Reaches for a real sleep to catch the pending branch in time
  • Leaves a failing path un-released, so a stray rejection lands on another test