skip to content

On a degraded link, why is asserting only that the screen eventually loads a weak case?

level: juniorimportance: should knowfreq 48%

answer

  1. Eventual success is the least interesting outcome
  2. Test the waiting, not just the arriving
  3. Acknowledge, block the second tap, bound the wait
  4. Force the failure leg deliberately
  5. Assert behaviour, never a duration

basics

~20 s

Eventual success only proves the happy path survives slowly. Assert what the user sees meanwhile and on failure: a progress surface, a control that cannot fire twice, a notice naming the next step, and typed data preserved.

solid answer

~40 s

Waiting long enough and then asserting the confirmation appeared tests the least interesting outcome. On a weak link the product's real promise is about the **in-between and the failure leg**. Assert four things instead. First, a progress surface appears promptly and disappears when the call settles. Second, the control that started the call cannot be triggered again while it is outstanding. Third, once the app's own deadline passes, a **failure notice on screen names what the user should do** - retry, check the connection, come back - rather than an indefinite wait. Fourth, whatever the app decides, typed data survives. Then force the failure leg deliberately by holding the impairment in place past the app's deadline; a case that never runs that leg has not tested degradation at all, only slowness.

code

pseudocode · 14 lines
pseudocode
case "degraded link surfaces the wait and the failure":
    impairment.apply(slow_lossy_profile)

    tap(submit)
    expect visible(progress_surface)      # acknowledgement
    tap(submit)                           # impatient second tap
    expect disabled(submit)

    impairment.hold_past(app_declared_deadline)
    expect visible(failure_notice)
    expect failure_notice.names_next_step()
    expect not visible(progress_surface)
    expect form_field(comment).value == typed_earlier
    expect server.submissions(for = user).count <= 1

go deeper

for a junior

Be ready to list what a degraded-link case should check beyond eventual success: an acknowledgement while waiting, a control that cannot be triggered twice, a bounded wait, and a failure notice that names a next step.

for a middle

Explain why durations make poor assertions under loss and how to force the failure leg deliberately by holding the impairment past the app's declared deadline, rather than waiting for an unlucky run.

for a senior

Show the judgment: pair the screen assertion with an effect count on the server side, assert the affordance rather than the internal attempt count, and spend these cases on flows where an ambiguous outcome costs something.

for a principal

Own where the deadline lives at all - a product-wide declared bound the screens inherit versus per-screen choices - and how that decision makes these cases writable and comparable across the product.

The instinct on a slow-link case is to raise the wait and assert the same thing the fast case asserts. That produces a case which is slower, more fragile and tests nothing new: it proves the happy path still completes, eventually, which was never in doubt. ## What the case is actually for A product's promise on a weak link is not "it works, slowly". It is a set of promises about what the user experiences while the outcome is unknown, and what they are told when it never arrives: - something acknowledges the action immediately, so the user does not think the tap was missed; - the action cannot be started twice by an impatient user; - the wait is bounded by a deadline the product chose, not by the transport giving up whenever it feels like it; - when the deadline passes, the screen says something actionable; - nothing the user typed is thrown away by any of this. Each of those is a testable assertion, and none of them is covered by "the confirmation eventually appeared". ## The four assertions worth writing 1. **Acknowledgement.** A progress surface, disabled control or inline indicator is visible while the call is outstanding, and gone once it settles. Assert both edges - a spinner that never clears is its own defect. 2. **No double action.** With the call outstanding, trigger the control again. The case then checks the server side: exactly one effect for one intended action. This is the assertion that catches the expensive class of bug, because on a fast link nobody ever manages to tap twice. 3. **A bounded, named failure.** Hold the impairment past the app's own deadline. A failure notice appears, it names the next step, and the screen is usable afterwards. An indefinite progress surface is a failing case even though nothing crashed. 4. **Preserved input.** After the failure, the form still holds what was typed and the flow can be resumed without re-entering it. ## Success leg versus failure leg | Leg | How the case creates it | What it proves | | --- | --- | --- | | Slow success | impairment applied, call allowed to complete | acknowledgement appears and clears; no duplicate effect from an impatient second tap | | Timed-out failure | impairment held past the app's declared deadline | the wait is bounded, the notice is actionable, typed input survives | Both legs are needed and the second is the one that gets skipped, because it is the one that requires a deliberate act. If your suite has only the first, you have tested slowness, not degradation. ## How this goes wrong in practice - **Raising the wait until it passes.** The case gets slower every release and its failure eventually means nothing, because a green result only proves the wait was long enough this time. - **Asserting the absence of an error.** "No error is shown" passes on a screen that hangs forever with a spinner, which is precisely the experience the case should reject. - **Checking the screen but not the effect count.** The confirmation looked right and two records were created; the user finds out at the end of the month. - **Confusing the product's own retry with the case.** The product may offer the user a way to try again, and asserting that affordance exists is legitimate. Asserting *how many times the product retried internally* pins an implementation detail and breaks on the next refactor. - **Testing this only on the flow that is easiest to automate.** The value is on flows where an ambiguous outcome costs something - a purchase, a submission, an upload - not on a settings screen. ## What the assertions look like as a set For one action on a degraded link, a complete case reads roughly like this: perform the action; assert the acknowledgement appears; attempt the action again and assert the control refuses; wait for the app's declared deadline to pass; assert the failure notice names a next step; assert the typed values are still present; assert the server side holds either zero or one effect but never two. That is five or six assertions on one flow, and none of them mention a duration. That matters: durations under loss vary, and a case built on them will fail for reasons that have nothing to do with the product. Behaviour - what is shown, what is disabled, what is preserved, how many effects exist - is stable across the whole spread of arrival times a degraded link produces, which is exactly what makes these assertions worth writing in the first place.

  • Is it ever right to assert a duration on a degraded-link case?
    Only as a coarse upper bound tied to a promise the product makes, such as an acknowledgement appearing within a second of the tap - and even then it is the local rendering that is being bounded, not the network round trip. Bounding the round trip itself converts normal variance under loss into false failures, which is why behaviour assertions are preferred throughout.
  • How do you assert the failure notice is actually useful rather than just present?
    Check that it names a next action the user can take - try again, check the connection, continue later - and that the screen remains usable afterwards. A notice that says only that something went wrong passes a presence check while leaving the user stuck, so the assertion is on the actionable part, not on exact wording, which changes with copy edits.
  • The product retries internally before showing anything. Does that change the assertions?
    Not the ones that matter. The acknowledgement must still appear immediately rather than after the internal attempts finish, the total wait must still be bounded by a declared deadline, and the failure notice must still arrive. What you avoid asserting is the number of internal attempts, since that is an implementation choice the product may change freely.

saying these in an interview costs you the question

  • Raises the wait until the slow case passes
  • Asserts only that no error was displayed
  • Never exercises the timed-out failure leg
  • Checks the screen but not the effect count
  • Pins how many times the product retried internally
  • Lets a failure wipe what the user typed