Search topics...
State MachinesFundamentalsintermediate

How do you test a state machine effectively?

0 upvotes
Practice with AISoon
Study the fundamentals first — State Machines topic page

The single best property of FSMs for testing is determinism: given the same starting state and event sequence, the FSM produces the same final state and same actions. No timing dependencies, no peripheral mocking, no flakiness.

The pattern is straightforward:

  1. Reset the FSM to a known starting state (via fsm_init or a test-only fsm_force_state helper)
  2. Inject a sequence of events by calling the dispatch function directly: fsm_handle_event(E_BUTTON)
  3. Assert the final state and that the right actions fired (ideally via mocks that record calls)

For coverage, aim for transition coverage: every (state, event) pair in your transition table should be exercised by tests at least once. With a 2D transition table, this is a generated loop:

c
for (int s = 0; s < NUM_STATES; s++) {
for (int e = 0; e < NUM_EVENTS; e++) {
fsm_init();
fsm_force_state(s);
State expected = fsm_table[s][e].next;
fsm_handle_event(e);
assert(fsm_get_state() == expected);
}
}

For switch-case or function-pointer FSMs, the same idea applies — iterate over enum values.

Beyond transition coverage, two more properties to check:

  • Invariants: for safety-critical FSMs, assert "whenever in state X, condition Y holds." Run the property check after every event injection.
  • Sequence behavior: longer event sequences exercise paths through the FSM. A handful of "happy path" and "error recovery" sequences as separate test cases.

The reason this works is the separation of event source from FSM logic. If your dispatch is a single function fsm_handle_event(Event e) with no peripheral side effects in the FSM itself (actions go through mockable interfaces), tests are pure functions of event sequences. The whole test suite runs in microseconds.

A common testing failure mode is hidden state: if the FSM reads a global variable or a peripheral register that the tests can't easily set up, you've broken the deterministic-input property. The fix is to pass that data as an event parameter or refactor to make it injectable.

Source: State Machines Q&A