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-jsonpathimport 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:
{
"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:
| Expression | Selects |
|---|---|
$.author.name | A nested field. |
$.tags[0] | An array element. |
$.comments[*].author | A field of every element. |
$.comments[?@.approved == true].author | A field of the elements that match a filter. |
$..id | Every id at any depth. |