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.

Example
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"}`))
}
Cookies

The simplest way to assert a response cookie is to provide the cookie name and value as parameters to the Cookie method.

Cookie("name", "value")
Example
package main

import (
	"net/http"
	"testing"

	"github.com/steinfletcher/apitest"
)

func TestAssertCookies(t *testing.T) {
	apitest.New().
		HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			http.SetCookie(w, &http.Cookie{Name: "name", Value: "value"})
			w.WriteHeader(http.StatusOK)
		}).
		Get("/data").
		Expect(t).
		Cookie("name", "value").
		End()
}

This is the opposite behaviour of CookiePresent and is used to assert that a cookie with a given name is not present in the response.

CookieNotPresent("Session-Token")
Example
package main

import (
	"net/http"
	"testing"

	"github.com/steinfletcher/apitest"
)

func TestAssertCookies_NotPresent(t *testing.T) {
	apitest.New().
		HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			http.SetCookie(w, &http.Cookie{Name: "name", Value: "value"})
			w.WriteHeader(http.StatusOK)
		}).
		Get("/data").
		Expect(t).
		CookieNotPresent("token").
		End()
}

Sometimes an application will generate a cookie with a dynamic value. If you do not need to assert on the value, use the CookiePresent method which will only assert that a cookie has been set with a given key.

CookiePresent("Session-Token")

apitest keeps a slice of cookies internally so you can invoke this method many times to assert on multiple cookies.

Example
package main

import (
	"net/http"
	"testing"

	"github.com/steinfletcher/apitest"
)

func TestAssertCookies_Present(t *testing.T) {
	apitest.New().
		HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			http.SetCookie(w, &http.Cookie{Name: "name", Value: "value"})
			w.WriteHeader(http.StatusOK)
		}).
		Get("/data").
		Expect(t).
		CookiePresent("name").
		End()
}
Struct

Cookies is a variadic function that can be used to take a variable amount of cookies defined as a struct.

Cookies(apitest.NewCookie("name").
	Value("value").
	Path("/user").
	Domain("example.com"))

The underlying fields of this struct are all pointer types. This allows the assertion library to ignore fields that are not defined in the struct.

Example
package main

import (
	"net/http"
	"testing"

	"github.com/steinfletcher/apitest"
)

func TestAssertCookies_Struct(t *testing.T) {
	apitest.New().
		HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			http.SetCookie(w, &http.Cookie{Name: "name1", Value: "value1", Path: "/path1", Secure: true})
			http.SetCookie(w, &http.Cookie{Name: "name2", Value: "value2", Path: "/path2", Secure: false})
			w.WriteHeader(http.StatusOK)
		}).
		Get("/data").
		Expect(t).
		Cookies(
			apitest.NewCookie("name1").Value("value1").Secure(true).Path("/path1"),
			apitest.NewCookie("name2").Value("value2").Secure(false).Path("/path2"),
		).
		End()
}
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.

Example
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.

Example
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")
Example
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

Example
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(),
).
Example
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"))
Example
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"))
Example
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))
Example
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
}
Example
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))
Example
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))
Example
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}$"))
Example
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"))
Example
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"))
Example
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"))
Example
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)
Example
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()
}