State Transition Testing
State Transition Testing models a system as a finite state machine and derives test cases from the valid and invalid transitions between states. It is essential for testing workflows, user account management, order processing, shopping carts, and any system where behavior depends on its current state. Many SDET interview questions involve drawing or testing state diagrams.
Core Concepts
- State: A distinct condition the system can be in (e.g., account status: Active, Locked, Suspended, Closed)
- Event/Trigger: An action that causes a state change (e.g., 'User enters wrong password 5 times')
- Transition: The change from one state to another triggered by an event
- Guard: A condition that must be true for a transition to occur
- Action: What happens during a transition (e.g., 'Send lockout notification email')
- 0-Switch Testing: Test every valid single transition (state → event → next state)
- 1-Switch Testing: Test every sequence of 2 transitions for deeper coverage
- Invalid Transitions: Test that events in wrong states are rejected, not processed
State Transition Testing for User Account
// ══════════════════════════════════════════════════════════════
// STATE DIAGRAM: User Account System
// ══════════════════════════════════════════════════════════════
//
// [INACTIVE] --register--> [PENDING]
// [PENDING] --verify_email--> [ACTIVE]
// [PENDING] --token_expired--> [INACTIVE]
// [ACTIVE] --wrong_password_x5--> [LOCKED]
// [ACTIVE] --admin_suspend--> [SUSPENDED]
// [ACTIVE] --delete_account--> [CLOSED]
// [LOCKED] --admin_unlock--> [ACTIVE]
// [LOCKED] --24h_elapsed--> [ACTIVE] (auto-unlock)
// [SUSPENDED] --admin_reinstate--> [ACTIVE]
// [CLOSED] → (terminal state, no transitions out)
// ══════════════════════════════════════════════════════════════
// TEST CASES from State Transition Table (0-Switch Coverage)
// ══════════════════════════════════════════════════════════════
// TC-ST-001: INACTIVE → register → PENDING
test("Registration moves account from INACTIVE to PENDING", async () => {
const account = await register("new@user.com", "Pass@123");
expect(account.status).toBe("PENDING");
expect(account.verificationEmailSent).toBe(true);
});
// TC-ST-002: PENDING → verify_email → ACTIVE
test("Email verification moves account from PENDING to ACTIVE", async () => {
const account = await verifyEmail(pendingUser.verificationToken);
expect(account.status).toBe("ACTIVE");
expect(account.canLogin).toBe(true);
});
// TC-ST-003: ACTIVE → wrong_password_x5 → LOCKED
test("5 wrong passwords locks an ACTIVE account", async () => {
for (let i = 0; i < 5; i++) {
await loginAttempt(activeUser.email, "wrongpass");
}
const account = await getAccount(activeUser.email);
expect(account.status).toBe("LOCKED");
expect(account.lockNotificationSent).toBe(true);
});
// TC-ST-004: LOCKED → admin_unlock → ACTIVE
test("Admin unlock restores LOCKED account to ACTIVE", async () => {
const account = await adminUnlock(lockedUser.id);
expect(account.status).toBe("ACTIVE");
expect(account.failedLoginAttempts).toBe(0); // reset on unlock
});
// TC-ST-005: INVALID TRANSITION — LOCKED account cannot login
test("LOCKED account cannot login even with correct password", async () => {
const result = await login(lockedUser.email, correctPassword);
expect(result.success).toBe(false);
expect(result.error).toBe("ACCOUNT_LOCKED");
// Account status should NOT change to ACTIVE on correct password
expect(lockedUser.status).toBe("LOCKED");
});
// TC-ST-006: INVALID TRANSITION — CLOSED account cannot register with same email
test("CLOSED account email cannot be re-registered", async () => {
const result = await register(closedUser.email, "NewPass@123");
expect(result.success).toBe(false);
expect(result.error).toBe("EMAIL_ASSOCIATED_WITH_CLOSED_ACCOUNT");
});Common Mistakes
- Only testing valid transitions — invalid transitions (attempting actions from wrong states) are where the most serious security bugs hide
- Not testing auto-transitions — time-based state changes (auto-unlock after 24h) are frequently missed and cause production incidents
- Missing terminal states — closed/deleted states often have hidden re-registration or re-activation bugs
- Not resetting state between test cases — test isolation is critical; a locked account from TC-003 must not affect TC-004's starting state
Tip
Tip
Practice State Transition Testing in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
Technical diagram.
Practice Task
Note
Practice Task — (1) Write a working example of State Transition Testing from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with State Transition Testing is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready software testing code.
Key Takeaways
- State Transition Testing models a system as a finite state machine and derives test cases from the valid and invalid transitions between states.
- State: A distinct condition the system can be in (e.g., account status: Active, Locked, Suspended, Closed)
- Event/Trigger: An action that causes a state change (e.g., 'User enters wrong password 5 times')
- Transition: The change from one state to another triggered by an event