requests

To configure the initial request into the system under test, you can specify request parameters such as the http method, url, headers and cookies.

apitest.New().
	Handler(handler).
	Method(http.MethodGet).
	URL("/user/12345")

This is quite verbose, so there are some shortcuts defined for the common http verbs that wrap up the Method and URL functions. The example can be more concisely defined as

apitest.Handler(handler).
	Get("/user/12345")

You can also define the request using the standard Go http.Request type.

req := httptest.NewRequest(http.MethodGet, "/user/1234", nil)
apitest.Handler(handler).
	Request(req)
Basic Auth

A helper method is provided to add preemptive basic authentication to the request.

BasicAuth("username", "password")
Example
package main

import (
	"net/http"
	"testing"

	"github.com/steinfletcher/apitest"
)

func TestRequests_BasicAuth(t *testing.T) {
	handler := func(w http.ResponseWriter, r *http.Request) {
		username, password, ok := r.BasicAuth()
		if !ok {
			w.WriteHeader(http.StatusBadRequest)
			return
		}

		if username != "username" || password != "password" {
			w.WriteHeader(http.StatusBadRequest)
			return
		}

		w.WriteHeader(http.StatusOK)
	}

	apitest.New().
		HandlerFunc(handler).
		Get("/hello").
		BasicAuth("username", "password").
		Expect(t).
		Status(http.StatusOK).
		End()
}
Body

There are two methods to set the request body - Body and JSON. Using Body the data will be copied to the raw request and wrapped in an io.Reader.

Post("/message").Body("hello")

JSON does the same and copies the provided data to the body, but the JSON method also sets the content type to application/json.

Post("/chat").JSON(`{"message": "hi"}`)

If you want to define other content types set the body using Body(data) and the header using Header.

Post("/path").
Body("<html>content</html>").
Header("Content-Type", "text/html")
JSON body example
package main

import (
	"io/ioutil"
	"net/http"
	"testing"

	"github.com/steinfletcher/apitest"
)

func TestRequests_JSONBody(t *testing.T) {
	handler := func(w http.ResponseWriter, r *http.Request) {
		data, _ := ioutil.ReadAll(r.Body)
		if string(data) != `{"a": 12345}` {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		if r.Header.Get("Content-Type") != "application/json" {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		w.WriteHeader(http.StatusOK)
	}

	apitest.New().
		HandlerFunc(handler).
		Post("/hello").
		JSON(`{"a": 12345}`).
		Expect(t).
		Status(http.StatusOK).
		End()
}
Text body example
package main

import (
	"io/ioutil"
	"net/http"
	"testing"

	"github.com/steinfletcher/apitest"
)

func TestRequests_TextBody(t *testing.T) {
	handler := func(w http.ResponseWriter, r *http.Request) {
		data, _ := ioutil.ReadAll(r.Body)
		if string(data) != `hello` {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		w.WriteHeader(http.StatusOK)
	}

	apitest.New().
		HandlerFunc(handler).
		Put("/hello").
		Body(`hello`).
		Expect(t).
		Status(http.StatusOK).
		End()
}
Cookies

There are multiple ways to specify http request cookies. These approaches are chainable.

Short form
Cookie("name", "value")
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.

Form

There are multiple ways to create a URL encoded form body in the request. The following approaches are chainable.

Multiple values

FormData is a variadic function that can be used to take a variable amount of values for the same key.

FormData("name", "value1", "value2")
Short form
FormData("name", "value")
GraphQL

The following helpers simplify building GraphQL requests.

Post("/graphql").
GraphQLQuery(`query { todos { text } }`).
Post("/graphql").
GraphQLRequest(apitest.GraphQLRequestBody{
	Query: "query someTest($arg: String!) { test(who: $arg) }",
	Variables: map[string]interface{}{
		"arg": "myArg",
	},
	OperationName: "myOperation",
}).
Example
package main

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"testing"

	"github.com/steinfletcher/apitest"
	"github.com/stretchr/testify/assert"
)

func TestRequests_GraphQLQuery(t *testing.T) {
	apitest.New().
		HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			bodyBytes, err := ioutil.ReadAll(r.Body)
			if err != nil {
				t.Fatal(err)
			}

			var req apitest.GraphQLRequestBody
			if err := json.Unmarshal(bodyBytes, &req); err != nil {
				t.Fatal(err)
			}

			assert.Equal(t, apitest.GraphQLRequestBody{
				Query: `query { todos { text } }`,
			}, req)

			w.WriteHeader(http.StatusOK)
		}).
		Post("/query").
		GraphQLQuery(`query { todos { text } }`).
		Expect(t).
		Status(http.StatusOK).
		End()
}

func TestRequests_GraphQLRequest(t *testing.T) {
	apitest.New().
		HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			bodyBytes, err := ioutil.ReadAll(r.Body)
			if err != nil {
				t.Fatal(err)
			}

			var req apitest.GraphQLRequestBody
			if err := json.Unmarshal(bodyBytes, &req); err != nil {
				t.Fatal(err)
			}

			expected := apitest.GraphQLRequestBody{
				Query:         `query { todos { text } }`,
				OperationName: "myOperation",
				Variables: map[string]interface{}{
					"a": float64(1),
					"b": "2",
				},
			}

			assert.Equal(t, expected, req)

			w.WriteHeader(http.StatusOK)
		}).
		Post("/query").
		GraphQLRequest(apitest.GraphQLRequestBody{
			Query: "query { todos { text } }",
			Variables: map[string]interface{}{
				"a": 1,
				"b": "2",
			},
			OperationName: "myOperation",
		}).
		Expect(t).
		Status(http.StatusOK).
		End()
}

Headers

There are multiple ways to specify http request headers. The following approaches are chainable.

Map
Headers(map[string]string{"name1": "value1", "name2": "value2"})
Params
Header("name", "value")
Intercept

Intercept is invoked pre request, allowing the implementer to mutate the request object before it is sent to the system under test. In this example we set the request params with a custom scheme.

package main

import (
	"net/http"
	"testing"

	"github.com/steinfletcher/apitest"
)

func TestIntercept(t *testing.T) {
	handler := func(w http.ResponseWriter, r *http.Request) {
		if r.URL.RawQuery != "a[]=xxx&a[]=yyy" {
			t.Fatal("unexpected query")
		}
		w.WriteHeader(http.StatusOK)
	}

	apitest.New().
		HandlerFunc(handler).
		Intercept(func(req *http.Request) {
			req.URL.RawQuery = "a[]=xxx&a[]=yyy"
		}).
		Get("/").
		Expect(t).
		Status(http.StatusOK).
		End()
}
Query Params

There are multiple ways to specify query parameters. These approaches are chainable.

Example
package main

import (
	"net/http"
	"testing"

	"github.com/steinfletcher/apitest"
)

func TestRequests_Query(t *testing.T) {
	expectedQueryString := "a=1&a=2&a=9&a=22&b=2"
	handler := func(w http.ResponseWriter, r *http.Request) {
		if expectedQueryString != r.URL.RawQuery {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		w.WriteHeader(http.StatusOK)
	}

	apitest.New().
		HandlerFunc(handler).
		Get("/foo").
		Query("a", "9").
		Query("a", "22").
		QueryCollection(map[string][]string{"a": {"1", "2"}}).
		QueryParams(map[string]string{"b": "2"}).
		Expect(t).
		Status(http.StatusOK).
		End()
}
Collection
QueryCollection(map[string][]string{"a": {"1", "2"}})

which results in parameters encoded as a=1&a=2.

Custom

If the provided approaches are not suitable you can define a request interceptor and implement custom logic.

apitest.New().
	Handler(handler).
	Intercept(func(req *http.Request) {
		req.URL.RawQuery = "a[]=xxx&a[]=yyy"
	}).
	Get("/path")
Map
QueryParams(map[string]string{"param1": "value1", "param2": "value2"})
Params
Query("param", "value")