How do you test a state machine effectively?
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:
- Reset the FSM to a known starting state (via
fsm_initor a test-onlyfsm_force_statehelper) - Inject a sequence of events by calling the dispatch function directly:
fsm_handle_event(E_BUTTON) - 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:
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
