Functional Fuzzer
You can leverage CATS super-powers of self-healing and payload generation in order to write functional tests.
This is achieved using the so called FunctionaFuzzer
using the cats run <file-name>
sub-command.
The FunctionalFuzzer
is not a Fuzzer
per se, but was named as such for consistency.
The functional tests are written in a YAML file using a simple DSL.
The DSL supports adding identifiers, descriptions, assertions as well as passing variables between tests.
The cool thing is that, by leveraging the fact that CATS generates valid payload, you only need to override values for a limited set of fields.
The rest of the information will be populated by CATS using valid data, just like a happy
flow request.
It's important to note that --refData
won't get replaced when using the FunctionalFuzzer
.
Any reference data must be supplied in the FunctionalFuzzer
file.
The FunctionalFuzzer
file has the following syntax:
/path:
testNumber:
description: Short description of the test
prop: value
prop#subprop: value
prop7:
- value1
- value2
- value3
oneOfSelection:
element#type: "Value"
expectedResponseCode: HTTP_CODE
httpMethod: HTTP_METHOD
And a typical run will look like (supposing the above file is named functionalFuzzer.yml
):
cats run functionalFuzzer.yml -c contract.yml -s http://localhost:8080
This is a description of the elements within the functionalFuzzer.yml
file:
- you can supply a
description
of the test. This will be set as theScenario
description. If you don't supply adescription
thetestNumber
will be used instead. - you can have multiple tests under the same path:
test1
,test2
, etc. expectedResponseCode
is mandatory, otherwise theFuzzer
will ignore this test. TheexpectedResponseCode
tells CATS what to expect from the service when executing this test.- at most one of the properties can have multiple values. When this situation happens, that test will actually become a list of tests one for each of the values supplied. For example in the above example
prop7
has 3 values. This will actually result in 3 tests, one for each value. - the tests are executed in the declared order. This is why you can have outputs from one test act as inputs for the next one(s) (see the next section for details).
- if the supplied
httpMethod
doesn't exist in the OpenAPI given path, awarning
will be issued and the test won't be executed - if the supplied
httpMethod
is not a valid HTTP method, awarning
will be issued and the test won't be executed - if the request payload uses a
oneOf
element to allow multiple request types, you can control which of the possible types theFunctionalFuzzer
will apply to using theoneOfSelection
keyword. The value of theoneOfSelection
keyword must match the fully qualified name of thediscriminator
. - if no
oneOfSelection
is supplied, and the request payload accepts multipleoneOf
elements, then a test will be created for each type of payload - the file uses Json path syntax for all the properties you can supply; you can separate elements through
#
or.
Dealing with oneOf, anyOf
When you have request payloads which can take multiple object types, you can use the oneOfSelection
keyword to specify which of the possible object types is required by the FunctionalFuzzer
.
If you don't provide this element, all combinations will be considered. If you supply a value, this must be exactly the one used in the discriminator
.
Correlating Tests
Suppose we have an endpoint that creates data (doing a POST
), and we want to check its existence (via GET
).
We need a way to get some identifier from the POST call and send it to the GET call.
The FunctionalFuzzer
input file can have an output
entry where you can state a variable name, and its fully qualified name from the response in order to set its value.
You can then refer the variable using ${variable_name}
from a subsequent test in order to use its value.
Here is an example:
/pet:
test_1:
description: Create a Pet
httpMethod: POST
name: "My Pet"
expectedResponseCode: 200
output:
petId: pet#id
/pet/{id}:
test_2:
description: Get a Pet
id: ${petId}
expectedResponseCode: 200
Suppose the test_1
execution outputs:
{
"pet":
{
"id" : 2
}
}
When executing test_1
the value of the pet id will be stored in the petId
variable (value 2
).
When executing test_2
the id
parameter will be replaced with the petId
variable (value 2
) from the previous case.
Variables are visible across all tests i.e. global variables; please be careful with the naming as they will get overridden.
Verifying Responses
The FunctionalFuzzer
can verify more than just the expectedResponseCode
. This is achieved using the verify
element. This is an extended version of the above functionalFuzzer.yml
file.
/pet:
test_1:
description: Create a Pet
httpMethod: POST
name: "My Pet"
expectedResponseCode: 200
output:
petId: pet#id
verify:
pet#name: "Baby"
pet#id: "[0-9]+"
/pet/{id}:
test_2:
description: Get a Pet
id: ${petId}
expectedResponseCode: 200
When running the above file:
- the
FunctionalFuzzer
will check if the response has the 2 elementspet#name
andpet#id
- if the elements are found, it will check that the
pet#name
has theBaby
value and that thepet#id
is numeric
The following json response will pass test_1
:
{
"pet":
{
"id" : 2,
"name": "Baby"
}
}
But this one won't (pet#name
is missing):
{
"pet":
{
"id" : 2
}
}
You can also refer to request fields in the verify
section by using the ${request#..}
qualifier. Using the above example, by having the following verify
section:
/pet:
test_1:
description: Create a Pet
httpMethod: POST
name: "My Pet"
expectedResponseCode: 200
output:
petId: pet#id
verify:
pet#name: "${request#name}"
pet#id: "[0-9]+"
It will verify if the response contains a pet#name
element and that its value equals My Pet
as sent in the request.
You can also supply boolean expression to be checked in the verify section using the checkBoolean
keyword:
/pet:
test_1:
description: Create a Pet
httpMethod: POST
name: "My Pet"
expectedResponseCode: 200
output:
petId: pet#id
verify:
pet#name: "${request#name}"
pet#id: "[0-9]+"
checkBoolean: T(java.time.LocalDate).now().isBefore(T(java.time.LocalDate).parse(expiry.toString()))
The checkBoolean
example also uses a SpEL expression to check that the returned expiry
is after the current date.
Dynamic expressions can be supplied in any section of the test case: either as input data (for example you might need a random identifier each time the test will run)
in the output sections (you might want to format the response fields before storing them in a variable) or in the verify section (applying different transformations before asserting).
Dynamic expressions can also refer variables or request/response fields internally. In the example above, expiry
is a field returned in the response.
If you want to refer a variable created in a previous test, let's call it petName
, you can do so as: T(org.apache.commons.lang3.StringUtils).substringAfterLast(${petName},'a')
.
You can also refer request elements using ${request#field}
.
Important notes:
verify
parameters support Java regexes as values- you can supply more than one parameter to check (as seen above)
- if at least one of the parameters is not present in the response, CATS will report an
error
- if all parameters are found and have valid values, but the response code is not matched, CATS will report a
warning
- if all the parameters are found and match their values, and the response code is as expected, CATS will report a
success
Working with additionalProperties in FunctionalFuzzer
You can also set additionalProperties
fields through the FunctionalFuzzer
file using the same syntax as for Setting additionalProperties in Reference Data.
Reserved Keywords
The following keywords are reserved in FunctionalFuzzer
tests: output
, expectedResponseCode
, httpMethod
, description
, oneOfSelection
, verify
, checkBoolean
, additionalProperties
, topElement
and mapValues
.