State Transition Testing
Model a system as a finite set of states and the events that move it between them. Test every valid transition — and verify that invalid transitions are rejected.
1 The Hook
A NZ courier company builds a parcel-tracking system. A parcel moves through states: Accepted, In Transit, Out for Delivery, Delivered. The team tests the obvious forward path and it all works — scan it in, move it along, mark it delivered. Customers are happy.
Then odd things start happening. A driver accidentally scans a parcel as "Out for Delivery" a second time after it was already marked Delivered, and the system cheerfully sends the customer a fresh "your parcel is on its way" text for a parcel sitting on their porch. Worse, a returned parcel gets scanned back to "In Transit" from "Delivered", and the proof-of-delivery record is silently overwritten. Nobody tested those moves because nobody asked "what happens if this event arrives when the parcel is in that state?"
The forward happy path is the easy 20%. The bugs were all in the transitions that should have been impossible — the events arriving in the wrong state. A system that only guards the steps it expects, and quietly accepts the ones it doesn't, ends up corrupting its own data. The only way to find these is to model every state and ask, for each event, whether it is allowed.
2 The Rule
Model the system as a fixed set of states and the events that move it between them, then test every valid transition at least once — and just as deliberately, test that invalid transitions are rejected rather than silently accepted.
3 The Analogy
The traffic lights at a NZ intersection.
A set of traffic lights has a small number of states — red, green, amber — and strict rules about which can follow which. Green goes to amber, amber goes to red, red goes to green. What must never happen is red jumping straight to green with no amber, or two directions both showing green at once. The whole safety of the intersection rests on the impossible transitions staying impossible.
State transition testing is checking the lights. You confirm every allowed change works — and then you spend real effort trying to force the forbidden ones, because a light that will skip amber when poked is the one that causes the crash. Testing only "green then amber then red" in order misses the dangerous case entirely.
What it is
Many systems can only be in one state at a time, and move between states in response to events. A bank account is either Open, Frozen, or Closed. A booking is Pending, Confirmed, Cancelled, or Completed. State transition testing ensures all these paths work correctly.
The model has four elements:
- States — the distinct conditions the system can be in.
- Events (triggers) — inputs or actions that cause a transition.
- Transitions — the move from one state to another.
- Actions — what the system does when a transition occurs.
Building the state model
Start with a state diagram (boxes = states, arrows = transitions labelled with event/action). Then convert it to a transition table for easier test case derivation.
Worked example
Online order lifecycle: an order starts as Pending, can be Confirmed, Dispatched, Delivered, or Cancelled.
| Current State | Event | Next State | Action |
|---|---|---|---|
| Pending | Payment received | Confirmed | Send confirmation email |
| Pending | Customer cancels | Cancelled | Send cancellation notice |
| Confirmed | Stock allocated & shipped | Dispatched | Send tracking number |
| Confirmed | Customer cancels | Cancelled | Initiate refund |
| Dispatched | Delivery confirmed | Delivered | Close order, request review |
| Delivered | Customer cancels | Invalid | Error: cannot cancel delivered order |
| Cancelled | Payment received | Invalid | Error: order is cancelled |
Coverage criteria
There are three main coverage levels:
- 0-switch (all states) — visit every state at least once. Weakest.
- 1-switch (all transitions) — exercise every valid transition at least once. ISTQB Foundation standard.
- 2-switch (all transition pairs) — every consecutive pair of transitions. Strong but expensive.
Testing invalid transitions
A complete test suite also tests transitions that should not be possible. Try to cancel a delivered order. Try to dispatch a cancelled order. These tests verify the system rejects invalid state changes gracefully — not silently, and not by corrupting data.
Real-world value: state transition bugs are nasty to find manually. A user who somehow gets an order into an impossible state can create support nightmares. Model it formally and test the invalid paths explicitly.
ISTQB mapping
| Ref | Topic | Level |
|---|---|---|
| 4.2.4 | State Transition Testing | CTFL Foundation |
| FL-4.2.4 K3 | Apply state transition testing to derive test cases | Foundation LO |
| FL-4.2.4 K3 | Achieve specified coverage levels | Foundation LO |
Practice this technique: Try Junior Practice 10 — Multi-step form flow, Junior Practice 04 — NZ checkout form, Senior Practice 04 — Session & auth edge cases.
NZ example — RealMe identity verification
RealMe is New Zealand’s government identity verification service (used by IRD, NZTA, MSD). A RealMe account moves through states during verification.
| Current State | Event | Next State | Notes |
|---|---|---|---|
| Unverified | Submit documents | Pending | Documents under review |
| Pending | Documents accepted | Verified | Identity confirmed |
| Pending | Documents rejected | Unverified | Resubmit required |
| Verified | Fraud flag raised | Suspended | Suspicious activity detected |
| Suspended | Fraud cleared | Verified | Account reinstated |
| Verified | Closure requested | Closed | Account closure |
| Unverified | Jump to Verified | Invalid | No documents submitted — must be impossible |
| Suspended | Self-close account | Invalid | Admin-only action; user cannot self-close while suspended |
All-transitions coverage requires a test case for each valid transition. The two invalid transitions must also be tested — verify the system rejects them gracefully, not silently.
NZ library book loan system — state transition table
A library book can be in one of four states: Available, On Loan, Reserved, or Overdue. There are exactly 6 valid transitions. For each row, select the From state and To state, and type the triggering event.
| # | From state | Event (what triggers the change?) | To state |
|---|
| # | From state | Event | To state |
|---|---|---|---|
| 1 | Available | Member borrows book | On Loan |
| 2 | On Loan | Member returns book | Available |
| 3 | Overdue | Member returns book | Available |
| 4 | On Loan | Due date passes / book not returned | Overdue |
| 5 | Available | Member reserves book | Reserved |
| 6 | Reserved | Member borrows / reservation fulfilled | On Loan |
4 Now You Try
Three graded exercises — spot, fix, then build. Write your answer, run it for AI feedback, then compare to the model answer.
A RealMe-style account has four states: Unverified, Pending, Verified, Suspended. The valid transitions are: Unverified→Pending (submit docs), Pending→Verified (docs accepted), Pending→Unverified (docs rejected), Verified→Suspended (fraud flag), Suspended→Verified (cleared). Identify three transitions that should be invalid and say what the system must do when each is attempted.
Show model answer
Any three of these well-explained earn full marks: 1. Unverified --jump straight to Verified--> Verified. No documents were ever submitted. The system must reject it; identity cannot be confirmed without the Pending review step. 2. Verified --submit documents--> Pending. Re-submitting docs for an already-verified account should be a no-op or rejected, not a silent move back to Pending that drops verified status. 3. Suspended --self-clear fraud--> Verified. A user cannot lift their own suspension; only an admin event (fraud cleared) may do that. 4. Unverified --fraud flag--> Suspended. There is nothing verified to suspend. In every case the rule is the same: the system must reject the transition gracefully — show an error or ignore it — and must NOT silently change state or corrupt the record. Silently accepting an impossible event is the bug.
A tester drafted the transition table below for a NZ online vehicle-registration renewal (states: Draft, Submitted, Paid, Complete). It is broken: one row has an impossible transition marked valid, and one genuinely valid transition is missing. Rewrite the table so it lists only valid transitions and add the missing one.
Draft — submit → Submitted (valid)
Submitted — pay → Paid (valid)
Complete — pay again → Paid (valid) ← suspicious
Paid — (nothing listed) ← something missing?
Rewrite as a correct transition table:
Show model answer
Correct valid-transition table: 1. Draft --submit--> Submitted 2. Submitted --pay--> Paid 3. Paid --confirmation issued--> Complete The impossible transition I removed: "Complete --pay again--> Paid". Once a renewal is Complete it is a terminal state; paying again must be rejected, not allowed to move backwards to Paid. Marking it valid would let a finished renewal be reopened and re-charged. The valid transition I added: "Paid --confirmation issued--> Complete". The original table had no way to reach the Complete state at all, so the renewal could never finish. Every non-terminal state needs at least one outgoing valid transition.
Design a complete state model for a KiwiSaver first-home withdrawal application. Define the states, list every valid transition (from state, event, to state), and name at least two invalid transitions the system must reject. Aim for all-transitions (1-switch) coverage.
Show model answer
A strong model (yours may differ in naming but should have the same shape): States: Draft, Submitted, Under Review, Approved, Declined, Paid Out. Valid transitions: 1. Draft --submit application--> Submitted 2. Submitted --provider begins review--> Under Review 3. Under Review --evidence accepted--> Approved 4. Under Review --evidence insufficient--> Declined 5. Approved --funds released--> Paid Out 6. Declined --applicant resubmits--> Submitted Invalid transitions to reject: 1. Submitted --funds released--> Paid Out. Cannot pay out before review and approval; must be rejected. 2. Paid Out --resubmit--> Submitted. Paid Out is terminal; a completed withdrawal cannot be reopened. 3. (bonus) Declined --funds released--> Paid Out. A declined application must never pay out. All-transitions coverage means one test per valid transition (six tests), plus explicit tests that each invalid transition is rejected gracefully without corrupting state.
Self-Check
Click each question to reveal the answer.
Q1: What are the four elements of a state transition model?
States (the distinct conditions the system can be in), events or triggers (inputs that cause a change), transitions (the move from one state to another), and actions (what the system does when a transition occurs).
Q2: Name the three main coverage levels and which one is the ISTQB Foundation standard.
0-switch (all states — visit every state once, weakest), 1-switch (all valid transitions — the ISTQB Foundation standard), and 2-switch (all transition pairs — strong but expensive). 1-switch / all-transitions is the expected baseline.
Q3: Why is testing invalid transitions just as important as testing valid ones?
Because the worst bugs are events arriving in a state where they should be impossible — a delivered parcel scanned back to in-transit, a completed payment re-charged. If the system silently accepts these, it corrupts its own data. You must verify it rejects them gracefully.
Q4: What does it mean for a state to be "terminal", and what must you check about it?
A terminal state (e.g. Completed, Paid Out, Closed) has no valid outgoing transitions — the workflow ends there. You must check that every event arriving in a terminal state is rejected, so a finished item cannot be reopened, re-charged, or moved backwards.
Q5: How do you turn a state diagram into test cases?
Convert the diagram (boxes = states, arrows = transitions) into a transition table listing current state, event, next state, and action. Each valid row becomes a test case for all-transitions coverage, and each impossible state/event pairing becomes an invalid-transition test.