A disconnected mobile client queues user actions and delivers them on reconnect - what does an automated case assert at each of the three moments?
answer
- Three moments, three sets of assertions
- Local acceptance while the link is gone
- Reconnection alone should start delivery
- Read the far side after it settles
- Device copy must match the far side
basics
~20 sAssert three times: while disconnected, that the action is locally accepted and marked pending; at reconnect, that delivery starts unprompted; once drained, that the far side holds the work and the device now matches it.
solid answer
~50 sSplit the case into three observation points instead of one end-to-end check. **While the device has no connection**, drive the action and assert the outcome the product promises offline - normally an optimistic local result plus a pending marker - and that nothing tells the user it failed. **At the reconnect boundary**, restore the link and assert that delivery begins because the link returned, not because the case navigated or restarted the application. **Once the queue has drained**, assert through a service interface - not through the screen, which renders from local storage - that every queued action landed on the far side and produced the value the stated ordering rule predicts, then read the device again and require the two to agree. Remove the connection with a control the run owns, and define settled as an observable condition rather than a fixed pause.
code
pseudocode · 22 linescase "queued edit is delivered after reconnect":
sign_in()
await(until: sync_idle, deadline: 30s)
# moment 1 - no connection
link.disable()
assert link.is_down()
ui.rename_item("item-42", "new name")
assert ui.label("item-42") == "new name"
assert ui.pending_marker("item-42") == true
assert ui.error_banner_shown() == false
# moment 2 - the link returns, nothing else happens
link.enable()
assert await(until: delivery_attempt_started, deadline: 30s)
# moment 3 - drained, both sides read
await(until: pending_count() == 0, deadline: 60s)
remote = service.get_item("item-42")
assert remote.name == "new name"
assert ui.label("item-42") == remote.name
assert ui.pending_marker("item-42") == falsego deeper
Be ready to say what a user should see the moment an action is taken with no connection: an immediate local result and a pending marker, not an error. Recall that the work is held on the device until the link returns.
Explain the three observation points and why each needs its own assertions - acceptance while disconnected, delivery starting at reconnect, and agreement between the device copy and the far side once the queue drains.
Show how you make the disconnection and the settled condition deterministic, how you prove the right number of effects landed, and how you keep the case diagnosable when it fails in an unattended run.
Own the promise the case defends: how long work created with no connection is retained, whether it survives the application being closed or reinstalled, and what the user is told when it can never be delivered.
A client that keeps working with no connection makes a promise in three parts: it accepts work now, it delivers that work later without being asked, and it ends up agreeing with the far side. A case that checks only the last part can pass while two of the three promises are broken, and when it fails it tells you nothing about which one broke. The repair is structural: one case, three observation points, each with its own assertions. ## Moment one - the action is taken with no connection Take the link away with a control the run owns, confirm it is actually gone, then drive the action through the same interface a user would. What you assert here is the **local contract**. The optimistic result the design promises is visible - the renamed row carries the new name. A **pending marker** distinguishes accepted-but-undelivered from delivered. And no failure message is shown. That last assertion matters more than it looks: a product that displays an error *and* quietly queues the work produces a user who retypes it, and the duplicate that follows is a genuine defect no far-side assertion will ever find. ## Moment two - the link returns Restore the connection and assert that **delivery is triggered by reconnection**, not by your case. If the case navigates, pulls to refresh, or restarts the application in order to make the queue drain, it has proved that a manual action drains the queue; it has not proved the product recovers on its own, which is the behaviour users depend on. Wait on an observable signal that an attempt started rather than on a fixed pause. ## Moment three - the queue has drained Now read the **far side** through a service interface, not the screen. The screen renders from local storage and will show the edit whether or not anything ever crossed the boundary. Assert on the far side that each queued action is present, that the resulting value is the one the product's stated ordering rule predicts, then read the device again and require the two to agree. Agreement is the property under test; either side alone is half a check. | Moment | What the case does | What it asserts | A failure here means | | --- | --- | --- | --- | | Disconnected | Drives the action | Optimistic result, pending marker, no error shown | Work is refused or dropped at capture | | Reconnect | Restores the link only | An attempt begins unprompted | The product needs a human to recover | | Settled | Reads both sides | Far side holds the work; device matches it | Work is lost, doubled, or only local | ## Determinism is the whole engineering problem Three things make this case unstable, and all three are yours to control. 1. **How the connection goes away.** A run-owned switch on the device, a proxy in the path the harness can black-hole, or a build hook that forces the transport layer to fail - any of these works, provided the case can confirm the link is really down before it acts and restores it during teardown even after a failure. A case that leaves a handset disconnected poisons every case that follows it on that device. 2. **When settled is.** Define it as an observable condition - the pending count reaches zero, an idle signal is raised - never as a duration. Durations only ever get tuned upward. 3. **What the baseline was.** Seed the record through a service interface with values the case chose, so the assertions can name exact strings instead of asserting that something changed. ## Details that separate a real case from a demonstration - **Assert the count, not merely the presence.** Three queued actions must produce three effects on the far side, not one. - **Cover the restart.** If the product promises queued work survives the application being closed, close and reopen it before restoring the link. Otherwise the case exercises an in-memory queue that no real user path matches. - **Cover rejection.** Delivery can fail because a rule on the far side refuses the write. Assert what the product does then, because the alternative is a user's work vanishing behind a green screen. - **Keep the diagnosis inside the failure.** Name the moment in the assertion text and capture the pending state plus the far-side response, so an unattended run reports which of the three promises broke rather than that the list looked wrong. ## What this case does not own It does not evaluate the design of the delivery mechanism, the scheduling policy, or the algorithm that decides ordering - those are design questions with their own home. The case takes the stated behaviour as given and proves the product delivers it: accepted while disconnected, delivered unprompted, converged afterwards. Written that way it survives a rewrite of the machinery underneath it, which is exactly what you want from a behavioural case.
- How do you remove the connection deterministically instead of asking someone to walk into a lift?Use a control the run owns and can verify: a device-level switch the automation drives, a proxy in the path the harness can black-hole, or a build hook that forces the transport layer to fail. Whichever you choose, the case must confirm the link is actually down before it acts, and restore it in teardown even when the case fails, or the next case on that device inherits the outage.
- The team wants one assertion at the end instead of three. What does that lose?A single end-state check cannot say which stage broke. If the final list is wrong you learn nothing about whether the action was never accepted offline, whether delivery never started, or whether the far side refused it. It can also pass for the wrong reason, when the product discarded the offline edit and the end state happened to match anyway. Three moments turn one red result into a diagnosis.
- Why read the far side through a service interface rather than trusting the screen after reconnect?The screen renders from local storage, so it can show the edit even if nothing ever left the device. Reading the far side proves the work crossed the boundary. Reading both and requiring them to agree is what proves convergence, rather than proving the device still remembers what the user typed.
Posting a letter from a village with no post office: you check it went in the bag, that the bag left when the road reopened, and that it arrived - three different failures, three different checks.
saying these in an interview costs you the question
- Asserts only the end state after the link returns
- Trusts the screen, which renders from local storage
- Drains the queue by hand, then calls delivery automatic
- Uses a fixed pause instead of an observable settled condition
- Treats work still pending as work that failed
- Never checks the count of effects on the far side