Stateful Mocks
When creating a mock API that mimics the behavior of a real-world API, simulating real-time behavior is essential. Often, this involves persisting state between two or more independent API calls. For instance, if a customer-ID is sent in one API request, it might need to be used in subsequent requests, even though it is not explicitly included in them. To address such scenarios, Beeceptor provides stateful mocks, which enable you to maintain and manage state across API calls.
Stateful mocks are especially useful for simulating workflows or operations that require data persistence, sequential logic, or dynamic responses based on previous requests. Beeceptor offers three types of stateful helpers to achieve this:
- Step Counter - A whole number that can be incremented, decremented, or reset.
- Data Store - A named key-value store that supports numbers, strings, and objects.
- List - A collection of values (numbers, strings, etc.) where duplicates are allowed. The values are retrieved in LIFO order.
Checkout the Movie Booking tutorial in the tutorials section for a detailed, use case–driven walkthrough of building stateful mocks.
Step Counter
The Step Counter is a numerical helper designed to maintain sequences across requests. You can use it to simulate incremental IDs, count the number of API calls, or track steps in multi-stage processes.
Syntax
Operation:
- Increment (
inc): Increments the counter by a given value (default is 1). This operation accepts both positive and negative values. If the specified counter (counterName) does not already exist, it will be created and initialized with the provided value. - Retrieve (
get): Retrieves the current value of the specified counter. - Set to specific value (
set): Updates counter to exact value. - Reset (
reset): Resets the counter to 0.
Counter Name:
- The counter name must be a string.
- It should be alphanumeric but can also include spaces,
:,-, and_. No other special characters are allowed. - The maximum length of the counter name is limited to 50 characters.
- It is case-sensitive, meaning
Counter1andcounter1would be treated as different counters.
Example
In the following example, unique order IDs are generated for an e-commerce system, where each new order is assigned a sequential order number. This template first performs an increment action to update the counter, and then retrieves the updated value to populate the JSON field.
{{step-counter 'inc' 'orderCounter' 1}}
{
"orderId": {{step-counter 'get' 'orderCounter'}},
"status": "Order Created"
}
Data Store
The Data Store acts as a key-value storage for persisting data between mock requests. It supports storing numbers, strings, and objects, making it versatile for simulating scenarios where previous request data needs to referred in future responses.
Syntax
Operation:
set: Stores a new value for the specifiedkeyName. Thevalueparameter is mandatory for this operation.get: Retrieves the value associated with the givenkeyName.
Example
In this example, the orderId from the incoming request payload is extracted and saved as lastOrder in the data store for future use. Here, in the first line, the orderId is dynamically captured from the request payload and stored in the data store with the key lastOrder.
API Response Template for POST /orders:
{{data-store 'set' 'lastOrder' (body 'orderId') }}
{
"orderId": "{{body 'orderId'}}",
"amount": 150.00,
"status": "Order Placed"
}
In another API endpoint, such as /orders/latest, you can retrieve the lastOrder from the persistent data store to return the most recently placed order.
API Response Template for GET /orders/latest:
{
"orderId": "{{data-store 'get' 'lastOrder'}}",
"amount": 150.00
}
List
The List helper allows you to manage collections of values such as arrays. You can add, remove, or retrieve items dynamically. Lists are particularly useful for simulating scenarios like queues, stacks, or shopping carts.
Syntax
Operations & Examples
push: Adds item to end of list. Returns nothing.pop: Removes and returns last item from list.get: Returns the list item at the given index. Negative indices are supported for reverse access. If no item exists at that index, the provided default value is returned. When called without an index, get returns the entire list.delete: Removes item by index. Returns nothing.reset: Clears the list. Returns nothing.size: Returns the size of the list as a number.shift: Removes and returns first item from list (queue behavior). Accepts optional default value if list is empty.unshift: Adds item to the start of the list. Returns nothing.contains: Checks if a value exists in the list. Returnstrueorfalse.push-unique: Adds item to end of list only if it doesn't already exist (set behavior). Returns nothing.update: Updates an item at the given index. Returns nothing.find: Searches the list for the specified value and returns its index. If the value does not exist in the list,-1is returned.
The following examples illustrate how these operators work. Assume the initial state of the list is [1, 2, 3].
| Operation | Example & Output | Description |
|---|---|---|
| push | {{ list 'push' 'myList' 4 }}Output: Nothing | Adds 4 at the end of the list.The list state after the execution: [1, 2, 3, 4]. |
| pop | {{ list 'pop' 'myList' }}Output: 4 | Removes and returns the last item. The list state after the execution: [1, 2, 3]. |
| get | {{ list 'get' 'myList' }}Output: [1, 2, 3] | Retrieves the entire list as comma-separated values. |
| get | {{ list 'get' 'myList' 1 }}Output: 2 | Retrieves the value at index 1. These are 0-based indexes. |
| get | {{ list 'get' 'myList' -1 }}Output: 3 | Retrieves the last item using negative index as -1. |
| get | {{ list 'get' 'myList' 5 'default' }}Output: default | Returns default value when index is out of bounds. |
| delete | {{ list 'delete' 'myList' 0 }}Output: Nothing | Removes the item at index 0.The list state after the execution: [2, 3]. |
| reset | {{ list 'reset' 'myList' }}Output: Nothing | Empties the list. The list state after the execution: []. |
| size | {{ list 'size' 'myList' }}Output: 3 | Returns the number of items in the list. |
| shift | {{ list 'shift' 'myList' }}Output: 1 | Removes and returns the first item.. |
| shift | {{ list 'shift' 'emptyList' 'default' }}Output: default | Returns default value when list is empty. |
| unshift | {{ list 'unshift' 'myList' 99 }}Output: Nothing | Adds 99 at the start of the list (at index 0). |
| contains | {{ list 'contains' 'myList' 2 }}Output: true | Checks if 2 exists in the list. |
| push-unique | {{ list 'push-unique' 'myList' 4 }}Output: Nothing | Adds 4 as it doesn't exist in the list.Repeating this won't add 4 again to the list. |
| update | {{ list 'update' 'myList' 1 'updated' }}Output: Nothing | Updates the item at index 1 to 'updated'.[0, 2, 3, 4] => [0, 'updated', 3, 4]. |
| find | {{ list 'find' 'myList' 3 }}Output: 2 | Returns the 0-based index of the given value. |
| find | {{ list 'find' 'myList' 5 }}Output: -1 | Returns -1 as if the given value isn't found. |
Using as a Stack
A stack operates on the Last-In, First-Out (LIFO) principle, the last item added is the first one removed. Use push to add an item to the top of the stack, and pop to remove the top item.
To view the current top item without removing it, use get with the index -1.
Push API
Consider a mock rule for POST /stack/push as below. This template pushes the item from request's payload into the stack named myStack and returns the updated stack details, including its size and the new top item.
{{ list 'push' 'myStack' (body 'item') }}
{
"action": "push",
"item": "{{body 'item'}}",
"stackSize": {{list 'size' 'myStack'}},
"topItem": "{{list 'get' 'myStack' -1}}",
"stack": {{{json (list 'get' 'myStack')}}}
}
Note: The use of triple curly braces ({{{ ... }}}) ensures that HTML encoding is not applied to the JSON output.
Pop API
The mock rule to pop items should have API defined as POST /stack/pop and response template as below. It removes the top item from myStack and returns the popped item, the updated stack size, and the new top element. If the stack becomes empty, the topItem field returns the message "Stack is empty".
{
"action": "pop",
"item": "{{list 'pop' 'myStack'}}",
"stackSize": {{list 'size' 'myStack'}},
"topItem": "{{list 'get' 'myStack' -1 'Stack is empty'}}",
"stack": {{{json (list 'get' 'myStack')}}}
}
Using as a Queue
A queue operates on a First-In-First-Out (FIFO) principle - the first item added is the first one processed or removed.
- Use
pushto enqueue (add) items to the end of the queue. - Use
shiftto dequeue (remove) items from the front. - Use
getwith index0to peek at the front item without removing it.
Enqueue API
API Response Template for POST /queue/enqueue:
{{ list 'push' 'taskQueue' (body 'taskName') }}
{
"action": "enqueue",
"task": "{{body 'taskName'}}",
"queueSize": {{list 'size' 'taskQueue'}},
"nextTask": "{{list 'get' 'taskQueue' 0}}",
"queue": {{{json (list 'get' 'taskQueue')}}}
}
Dequeue API
API Response Template for POST /queue/dequeue:
{
"action": "dequeue",
"task": "{{list 'shift' 'taskQueue' 'No tasks available'}}",
"queueSize": {{list 'size' 'taskQueue'}},
"nextTask": "{{list 'get' 'taskQueue' 0 'Queue is empty'}}",
"queue": {{{json (list 'get' 'taskQueue')}}}
}
Using as a Set
A set stores unique values, ensuring that no duplicates exist.
- Use
push-uniqueto add an item only if it doesn’t already exist in the set. - Use
containsto check whether a specific value is present.
For example, you can use a mock rule with endpoint POST /tags/add to collect unique tags. The following response template can be used to simulate a Set.
{{ list 'push-unique' 'productTags' (body 'tag') }}
{
"action": "add-tag",
"tag": "{{body 'tag'}}",
"totalTags": {{list 'size' 'productTags'}},
"allTags": {{{json (list 'get' 'productTags')}}}
}
This template adds a new tag to the productTags set if it isn’t already present. It returns the added tag, the total number of unique tags, and the complete list of tags in the set.
Using as an Array
An array represents an ordered collection of items that can be accessed and managed by index. You can perform operations such as add, update, find, and delete elements using array functions.
- Use
pushto add new items. - Use
updateto replace an existing item at a specific index. - Use
findto locate the index of an item. - Use
deleteto remove an item at a given index.
Example: API Response Template for POST /cart/add. The following template adds a productId to the shoppingCart array, and returns updated cart size, and the full cart contents.
{{ list 'push' 'shoppingCart' (body 'productId') }}
{
"action": "item-added",
"productId": "{{body 'productId'}}",
"cartSize": {{list 'size' 'shoppingCart'}},
"cart": {{{json (list 'get' 'shoppingCart')}}}
}
Example: API Response Template for PUT /cart/update. The following template updates an existing item in the cart at the specified index with the new productId, and returns the updated cart.
{{ list 'update' 'shoppingCart' (body 'index') (body 'productId') }}
{
"action": "item-updated",
"index": {{body 'index'}},
"newProductId": "{{body 'productId'}}",
"cart": {{{json (list 'get' 'shoppingCart')}}}
}
Example: API Response Template for GET /cart/find. The following template finds the position of a specific product in the cart. If the product is not found, index returns -1, and found is false.
{
"productId": "{{queryParam 'productId'}}",
"index": {{list 'find' 'shoppingCart' (queryParam 'productId')}},
"found": {{#if (eq (list 'find' 'shoppingCart' (queryParam 'productId')) '-1')}}false{{else}}true{{/if}}
}
Example: API Response Template for DELETE /cart/remove. The following template removes the item at the specified index from the shoppingCart array, returning the updated cart and its size.
{{ list 'delete' 'shoppingCart' (body 'index') }}
{
"action": "item-removed",
"index": {{body 'index'}},
"cartSize": {{list 'size' 'shoppingCart'}},
"cart": {{{json (list 'get' 'shoppingCart')}}}
}
Note: The triple curly braces ({{{ ... }}}) ensure that HTML encoding is not applied to the JSON output, allowing raw JSON to be returned.