apitest
Assertions

JSONPath

Assert on parts of a JSON response body with the apitest-jsonpath module.

JSONPath assertions check parts of a JSON body, which is useful when the response is large or contains values that change between runs. They live in a separate module, which keeps apitest itself free of dependencies.

go get github.com/steinfletcher/apitest-jsonpath
import jsonpath "github.com/steinfletcher/apitest-jsonpath"

Each function returns an assertion to pass to Assert:

apitest.Handler(handler).
	Get("/articles/12345").
	Expect(t).
	Status(http.StatusOK).
	Assert(jsonpath.Equal(`$.author.name`, "jon")).
	Assert(jsonpath.Contains(`$.tags`, "testing")).
	Assert(jsonpath.Len(`$.comments`, 2)).
	Assert(jsonpath.NotPresent(`$.author.password`)).
	End()

The examples on this page use this response:

GET /articles/12345
{
  "id": 12345,
  "title": "Testing Go HTTP services",
  "slug": "testing-go-http-services",
  "published": true,
  "author": { "name": "jon", "email": "jon@example.com" },
  "tags": ["go", "testing", "http"],
  "comments": [
    { "id": 1, "author": "ola", "approved": true },
    { "id": 2, "author": "kim", "approved": false }
  ]
}

Equal and NotEqual

Equal passes when the expression selects a value equal to the expected one:

apitest.Handler(handler).
	Get("/articles/12345").
	Expect(t).
	Assert(jsonpath.Equal(`$.title`, "Testing Go HTTP services")).
	Assert(jsonpath.Equal(`$.published`, true)).
	// JSON numbers decode as float64
	Assert(jsonpath.Equal(`$.id`, float64(12345))).
	End()

Numbers are float64

The body is decoded with encoding/json, so every JSON number becomes a float64. Compare numbers with float64(12345), not 12345.

Objects and arrays are compared as map[string]any and []any:

apitest.Handler(handler).
	Get("/articles/12345").
	Expect(t).
	Assert(jsonpath.Equal(`$.author`, map[string]any{
		"name":  "jon",
		"email": "jon@example.com",
	})).
	End()

NotEqual is the inverse:

apitest.Handler(handler).
	Get("/articles/12345").
	Expect(t).
	Assert(jsonpath.NotEqual(`$.author.name`, "ola")).
	End()

Contains

Contains passes when the expression selects an array that includes the expected value. Filter expressions select values from arrays of objects:

apitest.Handler(handler).
	Get("/articles/12345").
	Expect(t).
	Assert(jsonpath.Contains(`$.tags`, "go")).
	// filter expressions select from arrays of objects
	Assert(jsonpath.Contains(`$.comments[?@.approved == true].author`, "ola")).
	End()

Present and NotPresent

apitest.Handler(handler).
	Get("/articles/12345").
	Expect(t).
	Assert(jsonpath.Present(`$.slug`)).
	Assert(jsonpath.NotPresent(`$.author.password`)).
	End()

Present means non-empty

Present passes only for a non-empty value. A field that is null, false, 0, an empty string or an empty array counts as not present, and passes NotPresent. To check such a field, use Equal, for example jsonpath.Equal("$.published", false).

Matches

Matches checks a string, number or boolean against a regular expression. Numbers and booleans are formatted with fmt.Sprint before matching.

apitest.Handler(handler).
	Get("/articles/12345").
	Expect(t).
	Assert(jsonpath.Matches(`$.slug`, `^[a-z0-9-]+$`)).
	Assert(jsonpath.Matches(`$.author.email`, `@example\.com$`)).
	Assert(jsonpath.Matches(`$.id`, `^\d{5}$`)).
	End()

Length

Len checks the length of an array, object or string. GreaterThan and LessThan set bounds on it:

apitest.Handler(handler).
	Get("/articles/12345").
	Expect(t).
	Assert(jsonpath.Len(`$.tags`, 3)).
	Assert(jsonpath.GreaterThan(`$.tags`, 2)).
	Assert(jsonpath.LessThan(`$.comments`, 10)).
	End()

The bounds are inclusive

Despite their names, GreaterThan(expr, n) passes when the length is at least n, and LessThan(expr, n) passes when it is at most n.

Chain and Root

Chain groups several assertions into one:

apitest.Handler(handler).
	Get("/articles/12345").
	Expect(t).
	Assert(
		jsonpath.Chain().
			Equal(`$.author.name`, "jon").
			NotEqual(`$.author.name`, "ola").
			Present(`$.slug`).
			Contains(`$.tags`, "http").
			End(),
	).
	End()

Root is a chain whose expressions are relative to a common prefix, which removes repetition. A dot is inserted between the root and each expression, unless the expression starts with [ or ., so array elements can be addressed directly:

apitest.Handler(handler).
	Get("/articles/12345").
	Expect(t).
	Assert(
		jsonpath.Root(`$.author`).
			Equal(`name`, "jon").
			Matches(`email`, `^jon@`).
			End(),
	).
	Assert(
		jsonpath.Root(`$.comments`).
			Equal(`[0].author`, "ola").
			Equal(`[1].approved`, false).
			End(),
	).
	End()

Chains support Equal, NotEqual, Contains, Present, NotPresent and Matches.

JWT

JWTHeaderEqual and JWTPayloadEqual decode a JSON Web Token from the response and assert on its header or claims. They do not verify the signature.

The first argument selects the token from the response:

func fromAuthHeader(res *http.Response) (string, error) {
	return res.Header.Get("Authorization"), nil
}
apitest.Handler(handler).
	Post("/login").
	Expect(t).
	Status(http.StatusOK).
	Assert(jsonpath.JWTHeaderEqual(fromAuthHeader, `$.alg`, "HS256")).
	Assert(jsonpath.JWTPayloadEqual(fromAuthHeader, `$.sub`, "1234567890")).
	Assert(jsonpath.JWTPayloadEqual(fromAuthHeader, `$.name`, "John Doe")).
	End()

Matching mock requests

The apitest-jsonpath/mocks package provides the same assertions as mock matchers, for matching the JSON body of requests your handler sends to a mock. See custom matchers.

Expression syntax

Expressions are evaluated by PaesslerAG/jsonpath. Common forms:

ExpressionSelects
$.author.nameA nested field.
$.tags[0]An array element.
$.comments[*].authorA field of every element.
$.comments[?@.approved == true].authorA field of the elements that match a filter.
$..idEvery id at any depth.

On this page