State Machines in API Mocking
6 min read
Most mock servers return the same response every time you hit an endpoint. But real APIs don't work that way. An order starts as "pending," becomes "confirmed," then "shipped," and finally "delivered." A payment goes from "processing" to either "success" or "failed." A user registration flow moves through "unverified" to "verified."
This is where state machines come in. A state machine is a model that defines a set of states and the transitions between them. In API mocking, it allows your mock endpoints to return different responses based on the current state of a workflow—just like real APIs do.
Think of it like a board game. Each square is a state, and rolling the dice (making a request) moves you to the next square. The game remembers where you are, and what happens next depends on your current position.
Why State Machines Matter for API Testing
State machines solve a fundamental problem in API testing: most real-world APIs are stateful, but most mock servers are stateless.
Real-world applications include:
- E-commerce order tracking - Orders progress through pending, confirmed, shipped, and delivered states
- Payment processing - Transactions move from initiated to processing to success or failure
- User authentication flows - Registration, email verification, two-factor authentication sequences
- Document approval workflows - Draft, submitted, reviewed, approved, or rejected states
- Subscription management - Trial, active, past due, canceled states
- Async job processing - Queued, running, completed, or failed job statuses
- Booking systems - Pending, confirmed, checked-in, completed reservations
Without stateful mocking, testing these workflows requires either connecting to real backend services or writing complex test fixtures that manually track state.
The Challenge of Testing Stateful APIs
Testing stateful APIs locally presents several challenges:
1. State persistence across requests
Your frontend might make multiple requests during a single user flow. Each request expects the API to remember what happened before. Traditional mock servers can't do this—they treat each request in isolation.
2. Multiple valid paths through the same endpoint
A GET /orders/123 endpoint might return completely different data depending on whether the order is pending, shipped, or delivered. Configuring this with static mocks requires creating multiple endpoints or complex conditional logic.
3. Simulating realistic progression
Real APIs don't jump from "pending" to "delivered." They progress through intermediate states. Testing your UI's handling of each state requires controlled, predictable state transitions.
4. Branching workflows
Not all workflows are linear. A payment can succeed or fail. An approval can be accepted or rejected. Testing both paths requires the ability to control which branch the workflow takes.
5. Polling scenarios
Many UIs poll an endpoint repeatedly while waiting for a status change. Testing this requires a mock that can return the same state multiple times before eventually transitioning.
6. Coordinating multiple endpoints
Real workflows often involve multiple endpoints sharing the same state. The order status endpoint, shipping endpoint, and invoice endpoint all need to agree on the current order state.
How Mock Server State Machines Work
A state machine in API mocking consists of three core concepts:
States represent the different phases of your workflow. Each state is a named condition like "Pending," "Processing," or "Completed." Every state machine starts in an initial state and can transition to other states based on API interactions.
Transitions define how the system moves from one state to another. A transition connects two states and is triggered when certain conditions are met—typically when a specific endpoint is called.
State-aware responses allow each endpoint to return different data based on the current state. The same GET /orders/123 endpoint returns different JSON when the order is "pending" versus "shipped."
Here's how a typical order flow works:
Initial (Pending)
|
v
Confirmed
|
v
Shipped
|
v
Delivered
When you call GET /orders/123 in the "Pending" state, you get:
{ "status": "pending", "canCancel": true }
After the state advances to "Shipped," the same endpoint returns:
{ "status": "shipped", "trackingNumber": "TRK-789456" }
The endpoint URL never changes. The response changes based on the workflow's current state.
Practical Testing Scenarios
E-commerce checkout flow:
Test your entire checkout UI by creating a state machine with states for cart, payment processing, payment confirmed, and order placed. Each API call advances the state, letting you verify that your UI correctly handles each step.
Authentication sequences:
Build a login flow with states for logged out, credentials submitted, 2FA required, 2FA verified, and fully authenticated. Test how your app handles each authentication step, including error states like invalid credentials or expired 2FA codes.
Long-running job simulation:
Create a job processing flow where the status endpoint returns "queued" multiple times (without advancing), then "processing" several times, before finally returning "completed." This lets you test your polling logic and loading states.
Payment failure handling:
Design a branching flow where payment processing can lead to either "success" or "failed" states. Test both paths to ensure your UI correctly handles successful payments and displays appropriate error messages for failures.
Multi-level approval workflows:
Model complex approval chains where a document goes through multiple reviewers. Each approval or rejection triggers a different state transition, letting you test the full range of workflow outcomes.
Best Practices for Stateful API Mocking
Use descriptive state names. Name states based on business meaning: "PendingApproval," "PaymentReceived," "ShippedToCustomer." Avoid generic names like "State1" or "Step2." Clear names make your test scenarios self-documenting.
Separate concerns into different state machines. Create distinct state machines for unrelated workflows. An "OrderFlow" state machine shouldn't also handle "UserRegistration." This keeps each workflow focused and easier to understand.
Control state advancement explicitly. For polling endpoints, configure them to return the current state without advancing. Only action endpoints (like POST /orders/confirm) should trigger state transitions. This mirrors how real APIs behave.
Test all branches. For workflows with multiple possible outcomes, systematically test each path. Reset the state machine between test runs to ensure consistent starting conditions.
Add realistic delays. Different states often have different response times. A "processing" state might take 2 seconds to respond, simulating actual processing time, while a "completed" state responds instantly.
Design for observability. Use tools that show the current state in real-time. Watching state transitions as requests come in helps debug issues and verify that your application triggers the expected API calls.
Conclusion
State machines transform API mocking from simple request-response simulation into realistic workflow testing. By modeling the states and transitions of your backend services, you can test complex user journeys entirely locally—without waiting for backend teams or dealing with inconsistent test environments.
The key insight is that most interesting API behavior is stateful. Orders progress, payments complete, users authenticate. Effective testing requires mocks that can model this progression. State machines provide that capability, letting you verify that your application correctly handles every step of every workflow it depends on.
Whether you're building e-commerce flows, authentication systems, or any multi-step process, stateful mocking with state machines gives you the control and predictability that comprehensive testing requires.