Skip to content

API testing framework based on JSON test files, written in Go.

License

Notifications You must be signed in to change notification settings

TomWright/apitestr

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

apitestr

Go Report Card Documentation Test Build

A package used to run API tests defined in JSON files.

Usage

Install

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

Run the tests

apitestr -tests ./tests -base http://localhost:8080

Tests

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.

Groups

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.

Order

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.

Running Tests

Running a single test

// 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))
}

Running groups of tests

// 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

Checks are how you validate that the response returned is correct.

Body Equal

Checks that the body returned is exactly equal to the value given.

{
  "type": "bodyEqual",
  "data": {
    "value": "OK"
  }
}

JSON Body Equal

Checks that the body returned matches the given JSON object.

{
  "type": "jsonBodyEqual",
  "data": {
    "value": {
      "userId": 1,
      "id": 1,
      "title": "delectus aut autem",
      "completed": false
    }
  }
}

JSON Body Query Exists

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.

JSON Body Query Equal

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.

JSON Body Query Regex Match

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

Status Code Equal

Checks that the status code returned matches the given value.

{
  "type": "statusCodeEqual",
  "data": {
    "value": 200
  }
}

Custom Body Check

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"
  }
}

Creating your custom check

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.

Request Initialisation

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",
            }
        }
    }
}

Common init funcs

Some common init funcs are provided.

Request replacements

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:"
    }
}