Scenario
Scenario entity explained
Overview
The following sections will describe in details how to declare a Scenario, and for this example, we will create a mock for an HTTP POST endpoint that receives a body and creates a new user from it.
Check other examples at the Scenarios examples section.
Also, some fields are present only for certain protocols, please check the Supported Protocols section to see when an specific field is present and required/not required for the protocol.
Key
The Key object is used as an unique identifier to a group of Scenarios, it is composed by a
protocol
and its specific properties.
"key": {
"protocol": "HTTP",
"method": "POST",
"pathTemplate": "/users"
},
Every request received by Copycat will be parsed to a Scenario Key, and this key will be used to find the available Scenarios.
If no Scenario Key matches the given request Copycat will respond, based on the received protocol, with an error message informing that no Scenario was found for that request.
The key
field may vary according to the protocol
, check protocol specificities at Supported Protocols section.
Default Response
When no declared Scenario matches the received request, the defaultResponse declared for that group of Scenarios will be returned. The defaultResponse is a mandatory field.
"defaultResponse": {
"status": "400",
"headers": { "Content-Type": "application/json" },
"body": { "message": "Bad Request" }
},
Fallback Response
The Fallback Response will be returned if an error occurs while processing the request. This is an optional field designed to customize Copycat's behavior if it could not act as expected.
"fallbackResponse": {
"status": "500",
"headers": { "Content-Type": "application/json" },
"body": { "message": "Unexpected error creating a new user" }
},
Global Variables
The globalVariables
field is used to store values that will be available to all Scenarios.
These variables are evaluated only one time, before the analysis of the declared Scenarios and
are useful to store repeated values (specially if they are generated by a time consuming operation, like accessing the database)
or values that must be evaluated before the Scenario matching system kicks in.
"globalVariables":{
"validations": {
"invalidAge": "${request.body.age < 18 ? 'Age must be > 18' : null}",
"invalidType": "${request.body.type != 1 ? 'Invalid user type!' : null}"
}
},
Scenarios
All desired Scenarios should be declared as an array on the scenarios
field. Copycat will iterate
over this array, on the declared order, until it finds a match, and once found, the matching
scenario will be used to generate the response. This means that the Scenarios must be on the
correct order to work properly. Scenarios with a very generic filter, should probably be placed
at the end of the array.
"scenarios": [
{
"name": "Use real system",
"preConditions": [],
"request": {
"headers": {
"x-real-system": true
}
},
"redirect": {
"passThrough": {
"host": "http://real-app/users"
}
}
},
{
"name": "Bad request",
"preConditions": [ "${globalVariables.validations.values().stream().anyMatch(predicate((v)-> {v != null})) }" ],
"request": {},
"response": {
"status": "400",
"delay": 0,
"headers": { "Content-Type": "application/json" },
"body": {
"message": "${globalVariables.validations.values().stream().filter(predicate((v)-> {v != null})).collect(Collectors.toList())}"
}
},
"commands": []
},
{
"name": "Fail to create user - username not available",
"preConditions": [
"${db.getBy('users', {'username': request.body.username}) != null}"
],
"request": {},
"response": {
"status": "409",
"body": {
"message": "${format.string('username %s is not available',request.body.username)}"
}
}
},
{
"name": "Create new user - Success",
"request": {
"headers": {"Content-Type": "application/json"},
"queryParams": {},
"pathParams": {},
"body": {
"name": "#{[a-zA-Z]*}",
"username": "#{.*}",
"telephone": "#{[0-9]*}",
"age": "${request.body.age > 18}",
"type": "regular"
}
},
"response": {
"status": "201",
"delay": 0,
"headers": {},
"body": {
"id": "${idGenerator.seqId('users')}"
}
},
"callback": {
"type": "HTTP",
"typeProperties": {
"headers": {"Content-Type": "application/json"},
"url": "http://www.my-awesome-app.com/listener/users",
"method": "POST"
},
"payload": {
"id": "${response.body.id}"
},
"delay": 100
},
"commands": [
"${db.save('users', response.body.id, request.body)}"
]
}
]
Name
The Scenario's name
field should be used to tell what the Scenario is supposed to represent.
Think about it as a unit test method name, it should be clear and descriptive, to make easier
to find Scenarios, specially on long arrays.
This is a mandatory field, and it will not be used directly by Copycat's match logic.
"name": "Fail to create user - username not available",
Pre Conditions
preConditions
is an optional field that contains expressions that should be evaluated to booleans
and is the first thing that is checked when Copycat tries to match the received request with a
Scenario. All expressions on the array must evaluate to true
in order to proceed with the
matching process, otherwise the current Scenario will be discarded, and the matching process will
follow to check the next Scenario
"preConditions": [
"${db.getBy('users', {'username': request.body.username}) != null}"
],
Request
The request
field is used as a filter to check if the received request matches the Scenario's
declared request.
This field accepts Regular expressions, Code expressions and raw values.
"request": {
"headers": {"Content-Type": "application/json"},
"queryParams": {},
"pathParams": {},
"body": {
"name": "#{[a-zA-Z]*}",
"username": "#{.*}",
"telephone": "#{[0-9]*}",
"age": "${request.body.age > 18}",
"type": "regular"
}
},
It may vary according to the protocol, check protocol specificities at Supported Protocols section
Response
If a Scenario matches the received request, this is the field that will be used to generate the response that will be returned by Copycat.
This field accepts Code expressions and raw values.
"response": {
"status": "201",
"delay": 0,
"headers": {},
"body": {
"id": "${idGenerator.seqId('users')}"
}
},
The response
field may vary according to the protocol, check protocol specificities at Supported Protocols section.
Callback
After the response has been sent, a callback
can be scheduled. This optional field helps the
developer to mock applications that will trigger some events on a received request.
The scheduled callback does not have to be of the same protocol of the received request, but it is important to notice that the target of the callback must be accessible by Copycat, otherwise the execution will fail.
This field accepts Code expressions and raw values.
"callback": {
"type": "HTTP",
"typeProperties": {
"headers": {"Content-Type": "application/json"},
"url": "http://www.my-awesome-app.com/listener/users",
"method": "POST"
},
"payload": {
"id": "${response.body.id}"
},
"delay": 100
},
Variables
When some value is used many times, it can be replaced by a variable
, this is specially useful
if the repeated value is generated by a time-consuming operation (like accessing database).
For instance, on the example that is being used so far, a variable could be created for the generated id, as follows:
"variables": {"userId": "${idGenerator.seqId('users'))}"},
After that, the variable
could be accessed this way:
"body": {
"id": "${variables.userId}"
}
The declared variables will be available to use on response, commands and callback.
IMPORTANT:
The variables
field is very similar to globalVariables
but it is available only to the enclosing Scenario and it is
evaluated after the matching system.
PassThrough
A PassThrough is used to redirect the received request to the real system, the request and response will be recorded and can be used to create a customized scenario.
"redirect": {
"passThrough": {
"host": "http://real-app/users"
}
}
It is important to notice that the Pass Through target must be accessible by Copycat, otherwise the execution will fail.
Commands
It is possible to execute Code expressions through the commands field after a Scenario is elected. This is specially useful to change the storage state based on the elected Scenario (check Data Storage for more details).
"commands": [
"${db.save('users', response.body.id, request.body)}"
]