Skip to content
Bugwise
Menu
Testing Fundamentals

State Transition Testing

Learn to test features whose behavior depends on "where they are right now", like orders, logins with lockout and music players. Draw state diagrams and state tables, test valid and invalid transitions, and hunt bugs in a broken order system.

intermediate Mobile & web
Short on time? Switch to the quick version.

Maya orders a pair of running shoes. They arrive on Tuesday, she wears them on Wednesday, and on Thursday she opens the shop app out of curiosity. The Cancel order button is still there. She taps it. The app says “Order canceled”, and three days later $84 lands back on her card. She keeps the shoes.

Nobody wrote code that said “give away free shoes”. The developer simply forgot to ask one question: should Cancel still work when the order is already delivered? The button did the same thing in every situation, when it should have depended on where the order was in its journey.

That question is the whole idea behind state transition testingState transition testingTesting how the system moves between states, like Locked, Active and Suspended.Open in glossary →.

What is state transition testing?

Some features always react the same way: a calculator adds 2 + 2 the same way every time. Many others remember where they are, and the same action gives a different result depending on that. Tapping Cancel on a fresh order should cancel it. Tapping it on a delivered order should not.

State transition testing is a black-boxBlack-box testingTesting based only on inputs and outputs, without looking at the code.Open in glossary → test design technique for those features. You draw the places the system can be in (its states), the things that move it between them (events), and then you test both the moves that should happen and the ones that should not.

The building blocks

WordMeaningMusic player example
StateA situation the system is in, which changes how it reactsStopped, Playing, Paused
EventSomething that happens: a tap, a timer, a server messageThe user taps Pause
TransitionA move from one state to another, caused by an eventPlaying → Paused
GuardA condition that must be true for the transition to happenRefund only if delivered ≤ 30 days ago
ActionWhat the system does while it movesRemember the playback position

Here is the music player drawn as a state diagram. Boxes are states, arrows are transitions, and the label on each arrow is the event. The small dot with an arrow marks the initial state (where everything starts).

playstoppauseplaystopStoppedPlayingPaused
Music player: three states and five valid transitions.

Notice what is not on the diagram: there’s no arrow for “Pause while Stopped” or “Play while Playing”. Those are invalid transitions, and they are where a lot of bugs hide. Pressing Play twice in a row in a buggy player can start the same song twice, on top of itself.

The quick version

Two ways to write it down

A state diagram (like the one above) is great for seeing the flow. A state table is great for finding gaps. It lists every state as a row and every event as a column, so you’re forced to answer “what if this event happens in this state?” for every single combination:

State ↓ / Event →PlayPauseStop
StoppedPlaying✕ ignore✕ ignore
Playing✕ ignorePausedStopped
PausedPlaying✕ ignoreStopped

Every ✕ cell is an invalid transition: the event must be rejected or ignored, and the state must not change. Diagrams quietly hide those cells. Tables put them in front of you.

How much to test: three levels of coverage

  1. Every state. Make sure you visit each state at least once. The weakest level.
  2. Every valid transition. Fire each arrow on the diagram at least once. This is the usual minimum for a feature.
  3. Every invalid transition. Try each ✕ cell and check the system says no. This is where the expensive bugs are.

Where it shines

Anything with a status or a mode in a mobile or web app:

  • Orders: Placed → Paid → Shipped → Delivered, plus Canceled and Refunded. See shopping cart and checkout.
  • Login lockout: 0, 1, 2 wrong passwords, then Locked for 15 minutes. See login and sign-up.
  • Players and trackers: Stopped / Playing / Paused, or a workout that is Running / Paused / Finished.
  • Trips and deliveries: a ride that is Requested → Driver assigned → On trip → Completed.
  • Cards and accounts: Active, Blocked, Expired.

Typical bugs it finds

  • An invalid transition is allowed: canceling a delivered order, paying twice, refunding an unpaid order.
  • A stuck state: the order stays “Processing” forever after a network error, and no button gets you out.
  • The wrong state after restart: you pause a workout, the app is killed in the background, and it comes back “Running”. See background and foreground.

The building blocks in depth

States

A state is a situation that changes how the system reacts to the same input. That definition is the test for whether something deserves to be a state.

  • “The order is paid” is a state, because Pay, Cancel and Refund all behave differently once it’s true.
  • “The order has a blue shipping label” is not a state if nothing behaves differently because of it.

Two special kinds of state appear in almost every model:

  • The initial state is where the system starts: a new order is Placed, a fresh login screen has 0 wrong attempts. On a diagram it’s marked with a small filled dot and an arrow.
  • A final state is one the system never leaves: a Canceled or Refunded order. It’s drawn with a double border. Final states are great places to test, because every event there should be rejected.

Events and transitions

An event is anything that can happen to the system. Some come from the user (tap Cancel), some from time (15 minutes pass, the paid parking time runs out), and some from other systems (the courier’s scanner says “delivered”, the bank confirms a payment, a push notificationPush notificationA message an app sends to the device that appears even when the app is closed.Open in glossary → arrives).

A transition is the arrow: in state A, when event E happens, go to state B. A transition can also go back into the same state. A bank card that is Active stays Active after a successful payment, but something still happens: money moves. That’s called a self-transition.

Guards

A guard is a yes/no condition written in square brackets on an arrow, like refund [≤ 30 days since delivery]. The same event in the same state can go different ways depending on the guard:

  • Delivered + Refund [within 30 days] → Refunded
  • Delivered + Refund [after 30 days] → stays Delivered, with the message “The return window has closed”

Guards are where state transition testing meets boundary value analysisBoundary value analysisTesting values at and just around the edges of valid ranges, where bugs love to hide.Open in glossary →: test day 30 and day 31, not just day 5. And where several conditions combine (amount, country, membership level), a decision tableDecision tableA table listing combinations of conditions and the action the software should take for each.Open in glossary → is the better tool for the guard itself. See boundary value analysis and decision tables.

Actions

An action is what the system does during a transition: charge the card, send the tracking email, reset the counter, release the reserved stock. Written after a slash: cancel / refund payment.

Actions matter because a transition can land in the right state and still do the wrong thing. If canceling a paid order shows “Canceled” but never refunds the money, the state is correct and the customer is still out $84. Always check the state and the action in your expected resultExpected resultWhat should happen when you perform a test step, according to the requirements.Open in glossary →.

Drawing a state diagram from a spec

You don’t need special software. Paper, a whiteboard or any diagram tool works. Here’s the method, using an online shop’s order rules.

  1. Find the nouns that describe a status. Read the requirementsRequirementA statement of what the software must do or how well it must do it.Open in glossary → and highlight words like placed, paid, shipped, delivered, canceled, refunded. Those are your candidate states.
  2. Find the verbs and the timers. Pay, ship, deliver, cancel, refund, 30 days pass. Those are your events.
  3. Draw the happy path first. Placed → Paid → Shipped → Delivered.
  4. Add the side exits. Where can the order be canceled? Where can it be refunded? Add guards and actions as you go.
  5. Mark the initial and final states. Where does it start? Which states are dead ends?
  6. Turn it into a table and look at the empty cells. Every empty cell is a question for the product owner: “What should happen if a customer taps Cancel while the order is Shipped?” If nobody knows, you’ve found a gap in the spec before writing a single test.
paycancelshipcancel / refunddeliverrefund [≤ 30 days]PlacedPaidShippedCanceledRefundedDelivered
Online order: Placed is the initial state; Canceled and Refunded are final (double border).

State tables: where the bugs hide

The diagram above has 6 states and 5 events. That’s 6 × 5 = 30 combinations, but only 6 arrows. The other 24 combinations are invalid transitions. A diagram doesn’t show them, so nobody thinks about them. A state table does.

You’ll build the full order table in the explorer below (switch it to State table). Here’s a compact version of the most interesting rows:

StateCancelRefundPay
PlacedCanceled✕ nothing was paidPaid
PaidCanceled / refund✕ use Cancel✕ already paid
Shipped✕ offer a return✕ not delivered✕ already paid
Delivered✕ too lateRefunded [≤ 30 days]✕ already paid

For every ✕ cell, a correct system does three things:

  1. Stays in the same state. Nothing moves.
  2. Does no action. No money moves, no email is sent, no stock is released.
  3. Tells the user why (if the user caused it), or hides/disables the control in the first place. See buttons and their disabled stateDisabled stateA component that is visible but can't be used right now, usually shown greyed out.Open in glossary →.

How much to test: coverage levels

CoverageTest coverageHow much of the product, requirements or risks your tests actually check.Open in glossary → tells you how much of the model your tests have touched. From weakest to strongest:

LevelWhat you testOrder example
All statesEach state at least once6 states, reachable in 2 tests (one ending in Canceled, one in Refunded)
All valid transitionsEach arrow at least once6 arrows, coverable in 3 tests; the usual minimum
All invalid transitionsEach ✕ cell at least once24 cells, the high-value negative tests

Visiting every state is not enough. You can visit all six order states without ever trying “cancel a paid order”, and miss that it forgets to refund.

Where it shines in mobile and web apps

Order status is the classic. The same rules usually show up in three places: the customer app, the staff back office, and emails or push notifications. Each one is a door into the same state machine. Things to try:

More in shopping cart and checkout.

A login with lockout turns a counter into states: 0 wrong, 1 wrong, 2 wrong, Locked. It protects against a brute-force attackBrute-force attackGuessing passwords or codes over and over until one works.Open in glossary →.

  • The third wrong password locks the account (not the second, not the fourth: a boundary!).
  • The right password while Locked must still be rejected.
  • A successful login resets the counter. Does it? Test 2 wrong → right → log out → 1 wrong: the account must not lock.
  • After 15 minutes, can you log in again? What if you kill the app during the lockout?

More in login and sign-up.

A ride-hailing trip has states driven by three different people: the rider, the driver and the system.

acceptpick updrop offcancelcancel / feeRequestedDriver assignedOn tripCompletedCanceled
Ride-hailing trip: the rider can cancel before pickup; a fee applies once the driver has been on the way for more than 2 minutes.

A bank card in a banking app: Active, Blocked (frozen by the user) and Expired.

payblockunblock [ID check]expiry dateexpiry dateActiveBlockedExpired
Bank card: paying while Active is a self-transition; unblocking needs an ID check (a guard).
  • Paying while Blocked must be declined. This is the whole point of the freeze button.
  • Unblock needs Face ID or the PIN (the guard). Test it with the check failing.
  • An Expired card must not come back to life when someone taps Unblock.

Turning transitions into test cases

Each transition (valid or invalid) becomes one test caseTest caseA written set of steps, data and an expected result used to check one specific behavior.Open in glossary →. The structure maps neatly:

Test case partComes from
PreconditionPreconditionSomething that must already be true before you can start a test.Open in glossary →The start state (and how to get there)
StepThe event (and the guard value)
Expected resultThe end state + the action (or “stays, nothing happens”)
  1. Pick a transition, for example Paid + Cancel.
  2. Write the precondition as a path from the initial state: “Order placed and paid with a test card.”
  3. Write the step: “Tap Cancel order and confirm.”
  4. Write both halves of the expected result: “Status is Canceled and a refund of $84.00 appears in Payments.”
  5. Chain transitions to save time. One test can walk Placed → Paid → Shipped → Delivered → Refunded and check each step. Just make sure a failure in step 2 doesn’t hide steps 3 to 5.

Typical bugs

#BugWhat you seeTypical severity
1Invalid transition allowedDelivered order can be canceled and refundedCritical
2Paying twiceDouble-tap Pay, card charged twiceCritical
3Refund on an unpaid orderRefund issued for money never receivedCritical
4Lockout ignores the right passwordLocked account still logs in with the right passwordCritical
5Stuck stateOrder stays “Processing” forever after a network errorMajor
6Wrong state after restartPaused workout comes back as Running after the app is killedMajor
7Action missingOrder shows Canceled but no refund is madeMajor
8Final state revivedOld payment link re-opens a canceled orderMajor

Bugs 5 and 6 deserve a special mention for mobile. Apps are paused, killed and restored all the time, and the state must come back correctly from the server or from saved data. That’s state persistenceState persistenceWhether the app remembers what the user did after navigating away, rotating, or restarting.Open in glossary → and state restorationState restorationBringing the user back to the same screen and data after the OS closed the app in the background.Open in glossary →. Test transitions across a restart: start in Paused, kill the app, reopen, and check you’re still in Paused. See background and foreground and interruptions.

When you find one of these, the state table makes your bug report easy to write: start state, event, expected state, actual state.

When to use it (and when not to)

Use it when the same input gives different results depending on history: statuses, modes, counters, wizards, sessions, players, anything with “if it’s already X, then…”.

Skip it when the feature has no memory: a unit converter, a tip calculator, a search box. There, equivalence partitioningEquivalence partitioningSplitting inputs into groups that should behave the same, then testing one value from each group.Open in glossary → and boundary value analysisBoundary value analysisTesting values at and just around the edges of valid ranges, where bugs love to hide.Open in glossary → do the work.

Try it: the state machine explorer

This is the heart of the lesson. Pick a model, fire events, and watch the current state move. Arrows turn green once you’ve covered them. Then do what a good tester does: fire events that shouldn’t work and read what a correct system must do.

🧭 State machine explorer

Pick a model, fire events and watch where the system goes. Try to fill all three coverage meters.

A shop order from checkout to doorstep. Two final states: Canceled and Refunded.

paycancelshipcancel / refunddeliverrefund [≤ 30 days]PlacedPaidShippedCanceledRefundedDelivered

Current state

Placed

Fire an event

Tip: don't only follow the happy path. Fire events that "shouldn't" work, like Refund.

Coverage

States visited1/6
Valid transitions covered0/6

Guards tested as false: 0/1

Invalid transitions tried0/24

Every ✕ cell in the state table.

Event log

No events yet. Each line here is one test step.

Find the bug: a broken order system

Below is a shop’s back-office order page. The staff tool shows every action button all the time, so the server is responsible for rejecting the ones that aren’t allowed. Compare what it does with the order diagram and state table (the explorer above shows the spec). Use New order to start again. The notebook keeps your coverage across orders, so you can be systematic: try every action in every state.

🐞 Bug hunt: Order back office

This demo has 5 hidden bugs. Explore it like a tester, then check your findings.

Found0/5
shop.example.com/admin

Order

#A-1042

Placed
Trail running shoes × 1$74.00
Shipping$10.00
Total$84.00

Actions

Staff tool: every action is always shown. The server must reject the ones that aren't allowed.

Payments

No payments yet.

Net: $0.00

Status history

  1. Placed09:12

Tester's notebook

Coverage is measured against the spec diagram above, across all orders you create.

States
1/6
Valid
0/6
Invalid tried
0/24
  1. 1

    Hint: Get an order all the way to Delivered, then try the red buttons.

  2. 2

    Hint: On a brand-new order, look at what Refund does to Payments.

  3. 3

    Hint: What happens if you skip Record payment?

  4. 4

    Hint: Canceled is a final state. Is it really final?

  5. 5

    Hint: Try the same action twice in a row.

Practice: build a state table

Time to do it yourself. A parking app called ParkEasy lets drivers pay for street parking by the hour. Read its 7 rules and fill in the state table. Every cell is either the next state or ✕ (the event must be rejected).

🅿️ Exercise: the parking session state table

Read the spec, then fill every cell: which state does the session move to? Use ✕ when the event must be rejected.

Spec: ParkEasy app

  1. Tapping Start creates a session in Active.
  2. An Active session can be extended once → Extended.
  3. Only one extension per session is allowed.
  4. The driver can Stop an Active or Extended session → Ended.
  5. When the paid time runs out in Active or Extended → Expired.
  6. An Expired session can't be extended. Stop closes it → Ended (receipt is created).
  7. Ended is final: nothing changes it.

1. Pick a value

2. Tap cells to fill them

Parking session state table. Rows are current states, columns are events.
StateExtendStopTime runs out
Active
Extended
Expired
Ended
Tap a cell again with the same value to clear it.

Once your table is right, you’ve also written your test plan: 12 cells = 12 test cases, 6 positive and 6 negative. Some extra ideas a curious tester would add:

  • What if “Time runs out” and “Stop” happen in the same second? Which one wins, and is the driver charged correctly?
  • Does Extended survive killing the app and reopening it the next morning?
  • Is the receipt created exactly once when an Expired session is stopped?

Example test cases

TC-ST-01Order happy path: Placed to Delivered

Preconditions

  • Logged in as a customer
  • Cart with 1 item ($84.00 incl. shipping)

Steps

  1. Place the order
  2. Pay with a test card
  3. Staff marks it Shipped
  4. Courier marks it Delivered

Expected result

Status goes Placed → Paid → Shipped → Delivered. One charge of $84.00, a tracking email after Shipped, a push notification after Delivered.

TC-ST-02Cancel a paid order refunds the payment

Preconditions

  • Order is Paid ($84.00)

Steps

  1. Open the order
  2. Tap Cancel order and confirm

Expected result

Status is Canceled and a refund of $84.00 is created. Reserved stock is released.

TC-ST-03Cancel is rejected for a delivered order

Preconditions

  • Order is Delivered

Steps

  1. Open the order in the app
  2. Check whether Cancel is offered
  3. Send a cancel request another way (an older tab or the API)

Expected result

The app offers Return & refund instead of Cancel. The cancel request is rejected; status stays Delivered and no refund is created.

TC-ST-04Paying twice charges only once

Preconditions

  • Order is Placed
  • Network throttled to a slow 3G profile

Steps

  1. Double-tap Pay
  2. Open the order in a second tab and pay again

Expected result

Exactly one charge of $84.00. The second attempt shows "This order is already paid" and the status stays Paid.

TC-ST-05Right password is rejected while the account is locked

Preconditions

  • Test account exists
  • 3 wrong passwords entered, account is Locked

Steps

  1. Enter the correct password
  2. Tap Log in

Expected result

Login is refused with "Too many attempts. Try again in 15 minutes." The account stays Locked.

TC-ST-06Successful login resets the wrong-password counter

Preconditions

  • Test account exists
  • 0 wrong attempts

Steps

  1. Enter a wrong password twice
  2. Enter the right password
  3. Log out
  4. Enter a wrong password once

Expected result

After the last step the account is in "1 wrong" and is NOT locked.

TC-ST-07Refund is blocked after the return window (guard false)

Preconditions

  • Order Delivered 31 days ago

Steps

  1. Open the order
  2. Request a refund

Expected result

Refund is refused with "The return window has closed". Status stays Delivered; no refund is created. (Repeat with an order delivered exactly 30 days ago: refund is allowed.)

TC-ST-08Paused state survives the app being killed

Preconditions

  • Fitness app, a workout is Paused at 12:30

Steps

  1. Send the app to the background
  2. Kill it from the app switcher
  3. Reopen the app

Expected result

The workout is still Paused at 12:30, not Running and not lost.

Tip: click a test case to expand it.

Checklist

State transition testing checklist

0 of 12 checked

Check your understanding

Quick quiz

0/6 answered · 0 correct

  1. 1.In a music player, the user taps Pause while it's Playing and it moves to Paused. What is 'tap Pause' in state transition terms?

  2. 2.Which notation describes a guard?

  3. 3.Your tests visit all six order states. What can you honestly claim?

  4. 4.Why is a state table better than a diagram for finding invalid transitions?

  5. 5.A login locks after 3 wrong passwords. Which test is an invalid transition test?

  6. 6.Paid + Pay is fired. The status pill still says Paid. Is that enough to pass the test?

Key takeaways

  • Use state transition testing when the same action gives different results depending on where the system is: orders, logins, players, trips, cards.
  • The building blocks are states, events, transitions, guards (conditions in brackets) and actions (what happens during the move).
  • Diagrams show the flow; state tables expose every “what if this event happens in this state?” combination.
  • Coverage grows from all states to all valid transitions to invalid transitions. The invalid ones find the costly bugs.
  • Each transition becomes a test case: start state as precondition, event as step, end state plus action as expected result.
  • Watch for allowed invalid transitions, stuck states and the wrong state after an app restart.

Finished this page?Nice work, this page is complete!

Mark it as done to track your progress. It's saved in this browser, no account needed.

Its review cards are now in Bites ⚡
Esc

Type to search the whole site.