Behaviors
Behaviors modify responses before they are sent to the client. They enable latency simulation, response transformation, and dynamic content.
Adding Behaviors
Behaviors are added to responses using _behaviors:
{
"is": {
"statusCode": 200,
"body": "Hello"
},
"_behaviors": {
"wait": 1000,
"decorate": "function(request, response) { response.body += ' World'; return response; }"
}
}
Alternative Format: behaviors (without underscore)
Some tools generate behaviors without the underscore prefix. Both formats are supported:
{
"is": { "statusCode": 200 },
"behaviors": {
"wait": 1000
}
}
Alternative Format: behaviors as Array
Behaviors can also be specified as an array of behavior objects:
{
"is": { "statusCode": 200, "body": "Hello" },
"behaviors": [
{ "wait": 100 },
{ "decorate": "function(request, response) { response.body += ' World'; return response; }" }
]
}
When using array format, behaviors are merged into a single object. If the same behavior type appears multiple times, the last one takes precedence.
wait
Add latency to responses. Essential for testing timeout handling.
Fixed Delay
{
"_behaviors": {
"wait": 2000
}
}
Adds exactly 2000ms delay.
Random Delay
{
"_behaviors": {
"wait": {
"inject": "function() { return Math.floor(Math.random() * 1000) + 500; }"
}
}
}
Returns random delay between 500-1500ms.
JavaScript Function String
Some tools generate wait as a direct JavaScript function string:
{
"behaviors": [{
"wait": " function() { var min = Math.ceil(0); var max = Math.floor(100); return Math.floor(Math.random() * (max - min + 1)) + min; } "
}]
}
This format is supported and the function is evaluated to compute the delay.
Use Cases
Test client timeouts:
{
"stubs": [{
"predicates": [{ "equals": { "path": "/slow-endpoint" } }],
"responses": [{
"is": { "statusCode": 200 },
"_behaviors": { "wait": 5000 }
}]
}]
}
Simulate network latency:
{
"_behaviors": {
"wait": {
"inject": "function() { return Math.floor(Math.random() * 100) + 50; }"
}
}
}
decorate
Transform responses using JavaScript. The function receives request and response, and must return the modified response.
Basic Transformation
{
"is": {
"statusCode": 200,
"body": { "data": [] }
},
"_behaviors": {
"decorate": "function(request, response) { response.body.timestamp = Date.now(); return response; }"
}
}
Add Request Info to Response
{
"_behaviors": {
"decorate": "function(request, response) { \
response.headers = response.headers || {}; \
response.headers['X-Request-Path'] = request.path; \
response.headers['X-Request-Method'] = request.method; \
return response; \
}"
}
}
Conditional Modification
{
"_behaviors": {
"decorate": "function(request, response) { \
if (request.headers['X-Debug'] === 'true') { \
response.body = { \
original: response.body, \
debug: { path: request.path, query: request.query } \
}; \
} \
return response; \
}"
}
}
Parse and Modify JSON
{
"_behaviors": {
"decorate": "function(request, response) { \
var body = typeof response.body === 'string' ? JSON.parse(response.body) : response.body; \
body.serverTime = new Date().toISOString(); \
response.body = body; \
return response; \
}"
}
}
copy
Copy values from the request to the response. Useful for echoing request data.
Copy from Path
{
"is": {
"statusCode": 200,
"body": { "id": "${id}" }
},
"_behaviors": {
"copy": {
"from": { "path": "/users/(\\d+)" },
"into": "${id}",
"using": { "method": "regex", "selector": "$1" }
}
}
}
Request to /users/123 returns { "id": "123" }.
Copy from Query
{
"is": {
"statusCode": 200,
"body": "Page: ${page}"
},
"_behaviors": {
"copy": {
"from": "query",
"into": "${page}",
"using": { "method": "jsonpath", "selector": "$.page" }
}
}
}
Copy from Headers
{
"is": {
"statusCode": 200,
"headers": { "X-Request-Id": "${reqId}" }
},
"_behaviors": {
"copy": {
"from": "headers",
"into": "${reqId}",
"using": { "method": "jsonpath", "selector": "$['X-Request-Id']" }
}
}
}
Copy from Body
{
"is": {
"statusCode": 200,
"body": { "received": "${name}" }
},
"_behaviors": {
"copy": {
"from": "body",
"into": "${name}",
"using": { "method": "jsonpath", "selector": "$.user.name" }
}
}
}
Multiple Copies
{
"_behaviors": {
"copy": [
{
"from": { "path": "/orders/(\\d+)" },
"into": "${orderId}",
"using": { "method": "regex", "selector": "$1" }
},
{
"from": "query",
"into": "${format}",
"using": { "method": "jsonpath", "selector": "$.format" }
}
]
}
}
lookup
Look up data from external sources (CSV files, etc.).
CSV Lookup
{
"is": {
"statusCode": 200,
"body": { "name": "${name}", "email": "${email}" }
},
"_behaviors": {
"lookup": {
"key": {
"from": { "path": "/users/(\\d+)" },
"using": { "method": "regex", "selector": "$1" }
},
"fromDataSource": {
"csv": {
"path": "users.csv",
"keyColumn": "id"
}
},
"into": "${row}"
}
}
}
With users.csv:
id,name,email
1,Alice,alice@example.com
2,Bob,bob@example.com
Request to /users/1 returns { "name": "Alice", "email": "alice@example.com" }.
repeat
Control how many times a response is returned before cycling to the next response.
Basic Repeat
{
"responses": [
{
"is": { "statusCode": 200, "body": "First response" },
"_behaviors": { "repeat": 3 }
},
{
"is": { "statusCode": 200, "body": "Second response" }
}
]
}
The first response is returned 3 times before advancing to the second:
- Requests 1-3 → “First response”
- Request 4 → “Second response”
- Requests 5-7 → “First response” (cycles back)
- Request 8 → “Second response”
Per-Response Repeat
Each response can have its own repeat count:
{
"responses": [
{
"is": { "statusCode": 200, "body": "Success" },
"_behaviors": { "repeat": 5 }
},
{
"is": { "statusCode": 500, "body": "Error" },
"_behaviors": { "repeat": 2 }
}
]
}
Returns “Success” 5 times, then “Error” 2 times, then cycles.
Use Cases
Simulating rate limiting:
{
"responses": [
{
"is": { "statusCode": 200, "body": "OK" },
"_behaviors": { "repeat": 10 }
},
{
"is": {
"statusCode": 429,
"headers": { "Retry-After": "60" },
"body": "Rate limited"
}
}
]
}
Allows 10 requests, then returns 429, then cycles.
Simulating quota exhaustion:
{
"responses": [
{
"is": { "body": { "remaining": 100 } },
"_behaviors": { "repeat": 50 }
},
{
"is": { "body": { "remaining": 50 } },
"_behaviors": { "repeat": 50 }
},
{
"is": { "statusCode": 403, "body": "Quota exceeded" }
}
]
}
Testing retry logic with eventual success:
{
"responses": [
{
"is": { "statusCode": 503, "body": "Service unavailable" },
"_behaviors": { "repeat": 2 }
},
{
"is": { "statusCode": 200, "body": "Success after retries" }
}
]
}
Fails twice, then succeeds - perfect for testing retry mechanisms.
Without Repeat
Responses without repeat default to 1 - they are returned once before advancing:
{
"responses": [
{ "is": { "body": "First" } },
{ "is": { "body": "Second" } },
{ "is": { "body": "Third" } }
]
}
Each response is returned once in sequence (standard cycling).
Behavior Order
When multiple behaviors are defined, they execute in this order:
- copy - Copy request values into response
- lookup - Perform data lookups
- decorate - Transform the response
- wait - Add delay before sending
Combining Behaviors
{
"is": {
"statusCode": 200,
"body": { "userId": "${id}", "processed": false }
},
"_behaviors": {
"copy": {
"from": { "path": "/users/(\\d+)" },
"into": "${id}",
"using": { "method": "regex", "selector": "$1" }
},
"decorate": "function(request, response) { \
response.body.processed = true; \
response.body.timestamp = Date.now(); \
return response; \
}",
"wait": 100
}
}
Best Practices
- Use wait sparingly - Only for testing timeout handling
- Keep decorate functions simple - Complex logic is hard to debug
- Use copy for echoing - More maintainable than decorate for simple cases
- Test behaviors individually - Easier to debug
- Document behavior purpose - Future maintainers will thank you