Choose a test by the claim it needs to support. A function result, a widget interaction and a platform journey need different evidence, even when they describe the same feature.

Write the claim before choosing a tool

Start with an initial state, an action and an observable outcome. “Saving works” is too broad. “After an empty title is submitted, the editor stays open and explains the missing title” gives a reviewer something to check. Add the target and input method when they affect the outcome.

Flutter distinguishes unit, widget and integration testing. Locate the smallest environment that demonstrates the claim. A small test isolates a decision; it does not replace a target check when the problem crosses a platform boundary.

Build a three-row matrix

  1. List pure decisions: validation, conversion, sorting or state transitions.
  2. List visible interactions: input, tapping, dismissal, focus and error presentation.
  3. List boundaries: native services, persistence, a device or a remote dependency.
  4. Assign a check and expected observation to each required row.
  5. Mark unavailable environments before deciding what can be concluded.
One feature, three distinct claims
LayerSynthetic editor exampleEvidence
LogicReject an empty titleInput and validation result
WidgetShow error after submitState, action and assertion
TargetDraft survives restartDevice, saved draft and reopened screen

Work through a release blocker

In this synthetic example, validation and widget checks pass, but a draft disappears after reopening the app on one target. Passing rows narrow the investigation; they do not contradict the target failure. Inspect the persistence boundary and capture the same journey on baseline and candidate SDKs.

Consider the reverse: the target journey was never run because the device is unavailable. Write “not run” in that row. Do not copy a widget result into it. The reviewer can decide whether that gap blocks the delivery scope.

Keep setup data identical between comparisons. If a test substitutes a service, describe the substitution. A deterministic local fake can reproduce an interaction, but its success does not establish that the real service accepts the same request.

Keep the record reviewable

For each row record test name, SDK identity, app revision, command, exit result and relevant assertion. For a manual journey, record steps and observed outcome instead of inventing a test command. Attach only the logs needed to inspect the claim.

Revisit the matrix when a patch crosses another boundary. A validator change needs a different review from a platform plugin change. Keep the original failing example and a relevant boundary input, such as an empty value or repeated submit. The goal is a defensible answer to the blocker, not the largest test count.

Visual procedure

Logic: one decision; then Widget: one interaction; then Target: one real journey; then Decision: supported claims
Original illustration by Flock editorial: Turn a release blocker into a small test matrix: logic, widget behaviour and a target journey.

Logic: one decision; then Widget: one interaction; then Target: one real journey; then Decision: supported claims. This is an explanatory illustration, not a test result.

Primary sources

Next useful step

Find your next step

Browse the full sitemap