expectations
Several mechanisms are provided to verify the response. If none of these suit your needs you can provide custom Assert
functions. After defining the request, you must call Expect(t)
to begin defining expectations.
Body
To match the HTTP response body pass a string into the Body
method.
Expect(t).Body(`{"param": "value"}`)
The assertion library checks if the content is JSON and if so performs the assertion using testify's assert.JSONEq
method. If the content is not JSON, testify's assert.Equal
method is used.
package body
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
)
func TestAssertBody(t *testing.T) {
apitest.New().
HandlerFunc(handler).
Get("/greeting").
Expect(t).
Body(`{"message": "hello"}`).
End()
}
func handler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"message": "hello"}`))
}
Custom
Provide a custom assert function by implementing the signature fn func(*http.Response, *http.Request) error
.
Assert(func(res *http.Response, _ *http.Request) error {
if res.StatusCode >= 200 && res.StatusCode < 400 {
return nil
}
return errors.New("unexpected status code")
}).
Custom assert functions can be chained.
package body
import (
"errors"
"net/http"
"testing"
"github.com/steinfletcher/apitest"
)
func TestAssert_Custom(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
}).
Get("/data").
Expect(t).
Assert(isSuccess).
End()
}
func isSuccess(res *http.Response, _ *http.Request) error {
if res.StatusCode >= 200 && res.StatusCode < 400 {
return nil
}
return errors.New("unexpected status code")
}
Headers
There are two ways to specify HTTP response headers. The following approaches are chainable.
Map
Headers(map[string]string{"name1": "value1", "name2": "value2"})
Headers are stored internally in apitest in their canonical form. For example, the canonical key for "accept-encoding" is "Accept-Encoding".e content is JSON and if so performs the assertion using testify's assert.JSONEq
method. If the content is not JSON, testify's assert.Equal
method is used.
package body
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
)
func TestAssertHeaders_Map(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("name1", "value1")
w.Header().Set("name2", "value2")
w.WriteHeader(http.StatusOK)
}).
Get("/data").
Expect(t).
Headers(map[string]string{"name1": "value1", "name2": "value2"}).
End()
}
Params
Header("name", "value")
package body
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
)
func TestAssertHeaders_Params(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("name", "value")
w.WriteHeader(http.StatusOK)
}).
Get("/data").
Expect(t).
Header("name", "value").
End()
}
JSON Path
You can use JSONPath to assert on partial content from the response body. This is useful when you are only interested in particular fields in the response. A separate module must be installed which provides these assertions - go get -u github.com/steinfletcher/apitest-jsonpath
package jsonpath
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
"github.com/steinfletcher/apitest-jsonpath"
)
func TestJSONPath(t *testing.T) {
handler := http.NewServeMux()
handler.HandleFunc("/data", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{
"aValue": "0",
"anObject": {"a": "1", "b": 12345},
"matches": {
"anObject": {
"aString": "tom<3Beer",
"aNumber": 7.212,
"aBool": true
},
"aString": "tom<3Beer",
"aNumber": 7,
"aNumberSlice": [7, 8, 9],
"aStringSlice": ["7", "8", "9"],
"anObjectSlice": [{"key": "c", "value": "ABC"}]
}
}`))
})
apitest.New().
Handler(handler).
Get("/data").
Expect(t).
Assert(jsonpath.Equal("aValue", "0")).
Assert(jsonpath.NotEqual("aValue", "1")).
Assert(jsonpath.Present("aValue")).
Assert(jsonpath.NotPresent("x")).
Assert(jsonpath.Equal(`$.anObject`, map[string]interface{}{"a": "1", "b": float64(12345)})).
Assert(jsonpath.Contains(`$.matches.anObjectSlice[? @.key=="c"].value`, "ABC")).
Assert(
jsonpath.Root("matches").
Matches(`aString`, `^[mot]{3}<3[AB][re]{3}$`).
Matches(`aNumber`, `^\d$`).
Matches(`anObject.aNumber`, `^\d\.\d{3}$`).
Matches(`aNumberSlice[1]`, `^[80]$`).
Matches(`anObject.aBool`, `^true$`).
End(),
).
Assert(
jsonpath.Chain().
NotPresent("password").
NotEqual("aValue", "12").
End(),
).
End()
}
Chain
Provide several expectations at once
Assert(
jsonpath.Chain().
Equal("a", "1").
NotEqual("b", "2").
Present("c").
End(),
).
package jsonpath
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
"github.com/steinfletcher/apitest-jsonpath"
)
func TestJSONPath_Chain(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"a": "1", "b": "3", "c": "4"}`))
}).
Get("/data").
Expect(t).
Assert(jsonpath.Chain().
Equal("a", "1").
NotEqual("b", "2").
Present("c").
End()).
End()
}
Contains
When the selector returns an array type use Contains
. Given the JSON body in the response is {"id": 12345, "items": [{"available": true, "color": "red"}, {"available": false, "color": "blue"}]}
, we can select all color values which are available then assert that the value is contained in the result.
Assert(jsonpath.Contains("$.items[?@.available==true].color", "red"))
package jsonpath
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
"github.com/steinfletcher/apitest-jsonpath"
)
func TestJSONPath_Contains(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{
"items": [
{
"available": true,
"color": "red"
},
{
"available": false,
"color": "blue"
}
]
}`))
}).
Get("/data").
Expect(t).
Assert(jsonpath.Contains("$.items[?@.available==true].color", "red")).
End()
}
Equal
When the selector returns a single value use Equal
. Given the JSON body in the response is {"id": "12345"}
Assert(jsonpath.Equal("$.id", "12345"))
package jsonpath
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
"github.com/steinfletcher/apitest-jsonpath"
)
func TestJSONPath_Equal(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"message": "hello"}`))
}).
Get("/data").
Expect(t).
Assert(jsonpath.Equal("message", "hello")).
End()
}
Greater Than
Use GreaterThan
to enforce a minimum length on the returned value.
Assert(jsonpath.GreaterThan("$.items", 2))
package jsonpath
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
"github.com/steinfletcher/apitest-jsonpath"
)
func TestJSONPath_GreaterThan_LessThan(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"items": [3, 4]}`))
}).
Get("/data").
Expect(t).
Assert(jsonpath.GreaterThan("items", 1)).
Assert(jsonpath.LessThan("items", 3)).
End()
}
JWT Matchers
JWTHeaderEqual
and JWTPayloadEqual
can be used to assert on the contents of the JWT in the response (it does not verify a JWT).
func Test(t *testing.T) {
apitest.New().
HandlerFunc(myHandler).
Post("/login").
Expect(t).
Assert(jsonpath.JWTPayloadEqual(fromAuthHeader, `$.sub`, "1234567890")).
Assert(jsonpath.JWTHeaderEqual(fromAuthHeader, `$.alg`, "HS256")).
End()
}
func fromAuthHeader(res *http.Response) (string, error) {
return res.Header.Get("Authorization"), nil
}
package jsonpath
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
"github.com/steinfletcher/apitest-jsonpath"
)
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
func TestJSONPath_JWT(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Authorization", jwt)
w.WriteHeader(http.StatusOK)
}).
Get("/hello").
Expect(t).
Assert(jsonpath.JWTPayloadEqual(fromAuthHeader, `$.name`, "John Doe")).
Assert(jsonpath.JWTPayloadEqual(fromAuthHeader, `$.sub`, "1234567890")).
Assert(jsonpath.JWTPayloadEqual(fromAuthHeader, `$.iat`, float64(1516239022))).
Assert(jsonpath.JWTHeaderEqual(fromAuthHeader, `$.alg`, "HS256")).
Assert(jsonpath.JWTHeaderEqual(fromAuthHeader, `$.typ`, "JWT")).
End()
}
func fromAuthHeader(res *http.Response) (string, error) {
return res.Header.Get("Authorization"), nil
}
Len
Use Len
to check to the length of the returned value. Given the response is {"items": [1, 2, 3]}
, we can assert on the length of items like so
Assert(jsonpath.Len("$.items", 3))
package jsonpath
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
"github.com/steinfletcher/apitest-jsonpath"
)
func TestJSONPath_Len(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`{"items": [1, 2, 3]}`))
w.WriteHeader(http.StatusOK)
}).
Get("/hello").
Expect(t).
Assert(jsonpath.Len(`$.items`, 3)).
End()
}
Less Than
Use LessThan
to enforce a maximum length on the returned value.
Assert(jsonpath.LessThan("$.items", 2))
package jsonpath
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
"github.com/steinfletcher/apitest-jsonpath"
)
func TestJSONPath_GreaterThan_LessThan(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"items": [3, 4]}`))
}).
Get("/data").
Expect(t).
Assert(jsonpath.GreaterThan("items", 1)).
Assert(jsonpath.LessThan("items", 3)).
End()
}
Matches
Use Matches
to check that a single path element of type string, number or bool matches a regular expression.
Assert(jsonpath.Matches("$.a", "^[abc]{1,3}$"))
package jsonpath
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
"github.com/steinfletcher/apitest-jsonpath"
)
func TestJSONPath_Matches(t *testing.T) {
handler := http.NewServeMux()
handler.HandleFunc("/data", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{
"matches": {
"anObject": {
"aString": "tom<3Beer",
"aNumber": 7.212,
"aBool": true
},
"aString": "tom<3Beer",
"aNumber": 7,
"aNumberSlice": [7, 8, 9],
"aStringSlice": ["7", "8", "9"],
"anObjectSlice": [{"key": "c", "value": "ABC"}]
}
}`))
})
apitest.New().
Handler(handler).
Get("/data").
Expect(t).
Assert(
jsonpath.Root("matches").
Matches(`aString`, `^[mot]{3}<3[AB][re]{3}$`).
Matches(`aNumber`, `^\d$`).
Matches(`anObject.aNumber`, `^\d\.\d{3}$`).
Matches(`aNumberSlice[1]`, `^[80]$`).
Matches(`anObject.aBool`, `^true$`).
End(),
).
End()
}
Not Equal
NotEqual
checks that the json path expression value is not equal to given value
Assert(jsonpath.NotEqual("$.id", "56789"))
package jsonpath
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
"github.com/steinfletcher/apitest-jsonpath"
)
func TestJSONPath_NotEqual(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"message": "hello"}`))
}).
Get("/data").
Expect(t).
Assert(jsonpath.NotEqual("message", "hello1")).
End()
}
Not Present
Use NotPresent
to check the absence of a field in the response without evaluating its value.
Assert(jsonpath.NotPresent("password"))
package jsonpath
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
"github.com/steinfletcher/apitest-jsonpath"
)
func TestJSONPath_NotPresent(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"name": "jan"}`))
}).
Get("/user").
Expect(t).
Assert(jsonpath.NotPresent("password")).
End()
}
Present
Check the presence of a field in the response without evaluating its value.
Assert(jsonpath.Present("token"))
package jsonpath
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
"github.com/steinfletcher/apitest-jsonpath"
)
func TestJSONPath_Present(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"token": "f9a5eb123c01de"}`))
}).
Post("/login").
Expect(t).
Assert(jsonpath.Present("token")).
End()
}
Root
Root
is used to avoid duplicated paths in body expectations. For example, instead of writing:
Assert(jsonpath.Equal("$.a.b.c.d", "a")).
Assert(jsonpath.Equal("$.a.b.c.e", "b")).
Assert(jsonpath.Equal("$.a.b.c.f", "c")).
it is possible to define a root path like so
Assert(
jsonpath.Root("a.b.c").
Equal("d", "a").
Equal("e", "b").
Equal("f", "c").
End(),
)
Status code
Use the Status
method to match on the http status code.
Expect(t).Status(http.StatusOK)
package jsonpath
import (
"net/http"
"testing"
"github.com/steinfletcher/apitest"
)
func TestAssertions_StatusCode(t *testing.T) {
apitest.New().
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}).
Get("/ping").
Expect(t).
Status(http.StatusOK).
End()
}