A package used to run API tests defined in JSON files.
Download an executable from the latest release.
You may have to chmod +x
the download file.
Or build from source:
git clone git@github.com:TomWright/apitestr.git
cd apitestr
go build -o apitestr cmd/app/main.go
apitestr -tests ./tests -base http://localhost:8080
Tests are contained in a single JSON file - Example test here.
A test belongs to a single group and can be ordered within that group.
Tests will execute a single request whose response is then validated by a list of checks.
Tests are executed group by group.
You can add a test to a group using the group
JSON key. If no group is provided then default
is used.
If you need your tests executed in a specific order you can use the order
JSON key. If no order is provided then 0
is used.
Tests with the same group and order will be run at the same time.
// a context is always required
ctx := context.Background()
// set the base url to be used with the test
ctx = testr.ContextWithBaseURL(ctx, "https://example.com")
// parse the test file
t, err := parse.File(ctx, "path/to/my/test.json")
if err != nil {
panic(err)
}
// run the test
err := testr.Run(t, nil)
// handle the error if the test failed
if err != nil {
panic(fmt.Errorf("test `%s` failed: %s\n", t.Name, err))
}
// a context is always required
ctx := context.Background()
// set the base url to be used with the test
ctx = testr.ContextWithBaseURL(ctx, "https://example.com")
// parse the test files
tests := make([]*testr.Test, 2)
var err error
tests[0], err = parse.File(ctx, "path/to/my/test1.json")
if err != nil {
panic(err)
}
tests[1], err = parse.File(ctx, "path/to/my/test1.json")
if err != nil {
panic(err)
}
res := testr.RunAll(testr.RunAllArgs{}, tests...)
// log the results
log.Printf("tests finished\n\texecuted: %d\n\tpassed: %d\n\tfailed: %d", res.Executed, res.Passed, res.Failed)
Checks are how you validate that the response returned is correct.
Checks that the body returned is exactly equal to the value given.
{
"type": "bodyEqual",
"data": {
"value": "OK"
}
}
Checks that the body returned matches the given JSON object.
{
"type": "jsonBodyEqual",
"data": {
"value": {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
}
}
Queries the JSON body using gjson and ensures that the queried element exists.
{
"type": "jsonBodyQueryExists",
"data": {
"query": "title"
}
}
There is an optional dataId
property you can set in the data object of this check. If this property is not empty, the value found by this check will be stored under the given dataId
for use by subsequent tests.
Queries the JSON body using gjson and ensures that the queried element has a value equal to the one specified.
{
"type": "jsonBodyQueryEqual",
"data": {
"query": "title",
"value": "delectus aut autem"
}
}
There is an optional dataId
property you can set in the data object of this check. If this property is not empty, the value found by this check will be stored under the given dataId
for use by subsequent tests.
Queries the JSON body using gjson and ensures that the queried element matches the given regex pattern.
{
"type": "jsonBodyQueryRegexMatch",
"data": {
"query": "title",
"pattern": "([a-z]{8}) ([a-z]{3}) ([a-z]{5})"
}
}
There is an optional dataIds
property you can set in the data object of this check. If this property is not empty, the values found in matching groups by this check will be stored under the given dataIds
for use by subsequent tests.
E.g. This response:
{
"message": "Hello there, Tom"
}
With this check:
{
"type": "jsonBodyQueryRegexMatch",
"data": {
"query": "message",
"pattern": "([a-zA-Z]+), ([a-zA-Z]+)",
"dataIds": {
"0": "responseMessage",
"1": "responseGreeting",
"2": "responseName"
}
}
}
Will result in the following variables being available for use later on:
$.responseMessage
:Hello there, Tom
$.responseGreeting
:Hello there
$.responseName
:Tom
Checks that the status code returned matches the given value.
{
"type": "statusCodeEqual",
"data": {
"value": 200
}
}
Reads the response body into a byte array and provides it to your custom function to validate, identified by the id
value.
{
"type": "bodyCustom",
"data": {
"id": "123check"
}
}
First create your custom validator func - this must implement the check.BodyCustomCheckerFunc
interface.
var custom123 check.BodyCustomCheckerFunc = func(bytes []byte) error {
if string(bytes) != "[1,2,3]" {
return fmt.Errorf("response is not 1,2,3")
}
return nil
}
Then register the custom func against your context.
ctx = testr.ContextWithCustomBodyCheck(ctx, "123check", custom123)
Then simply use the id of 123check
in your bodyCustom
check data.
Sometimes you'll need to do some dynamic testing, and that's where request init functions come in.
Register your init funcs as follows:
var myInitFunc RequestInitFunc = func(ctx context.Context, req *http.Request, data map[string]interface{}) (*http.Request, error) {
// modify or create a new request here
// use the data map as required
return req, nil
}
testr.ContextWithRequestInitFunc(ctx, "my-init-func-id", myInitFunc)
And then use them in your tests as so:
{
"request": {
"init": {
"my-init-func-id": {
"some": "data",
}
}
}
}
Some common init funcs are provided.
The replacements init func allows you to replace placeholders in the request URL path, URL query, headers and body with a given value.
It can be registered as follows:
testr.ContextWithRequestInitFunc(ctx, "replacements", testr.RequestReplacements)
Used in tests as so:
{
"request": {
"init": {
"replacements": {
":name:": "Tom",
}
},
"url": "https://example.com/users?name=:name:"
}
}
If you want to use a data value that has been stored in the context by another test you should use $.my-data-item
as the replacement value, where the previous test had used my-data-item
as the dataId
.
Or, moving on from the example given previously in JSON Body Query Regex Match you would do something like this:
{
"request": {
"init": {
"replacements": {
":name:": "$.responseName",
}
},
"url": "https://example.com/users?name=:name:"
}
}