httptester

package module
v0.0.0-...-d6116c7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 7, 2023 License: MIT Imports: 16 Imported by: 0

README

httptester

Go Reference

An opinionated API for making assertions against HTTP servers in high-level tests for Golang.

The goal here is to make for succinct yet descriptive tests against an HTTP application. Support is mainly focussed around JSON APIs.

As with many Go projects, this started out as a package within a personal project, but I moved this out to reuse elsewhere. Feel free to use it too, but consider it experimental.

package my_server_test

import (
	"github.com/vaeryn-uk/go-httptester"
	"net/http"
	"testing"
)

func TestMyServer(t *testing.T) {
	// Initialise a handler which implements your HTTP application.
	var serverToTest http.Handler

	// Initialises an HTTP server via httptest.NewServer that will automatically close
	// when the test is ended.
	srv := httptester.Server(t, serverToTest)

	// Create a new tester to test an HTTP request + response.
	ht := httptester.New(t, srv)

	// Define a request.
	ht.Request(
		"GET",
		"/",
		// Configure the request further here, e.g. adding a bearer token.
		ht.Bearer("some access token"),
		// Or a JSON body.
		ht.JsonBody("some data"),
	).Expect(
		// Use ExpectXXX() to set up assertions against the resulting response.
		// Like its return code.
		ht.ExpectCode(200),
		// Or that the raw body contains some string.
		ht.ExpectBodyContains("Fake Street"),
		// Or that a jsonpath expression exists
		ht.ExpectJsonExists("$[0].name"),
		// Or that a jsonpath expression resolve to some specific value
		ht.ExpectJsonMatchStr("$[0].name", "Scotty"),
	// Finally invoke Test() to perform the test.
	).Test("optional additional info here will be printed on test failure")
}

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var MaxReqRespOutput = 1200

MaxReqRespOutput is used when reporting test failures. The maximum amount of request or response output is printed.

Functions

func DataContains

func DataContains(t TestingTB, data any, pathexpr string, extra ...any) any

DataContains fatals if the provided data does not contain a value at pathexpr, as per JSONPath. This is like JsonContains, but does not assume a JSON string, instead checking against the provided parsed data.

func JsonContains

func JsonContains(t TestingTB, data string, pathexpr string, extra ...any) any

JsonContains fatals the test if the provided JSON data does not contain a value at pathexpr, as per JSONPath. https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html Returns the resolved value.

func JsonContainsStr

func JsonContainsStr(t TestingTB, data string, pathexpr string, extra ...any) string

JsonContainsStr fatals the test if the provided JSON data does not contain a string value at pathexpr, as per JSONPath. https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html Returns the resolved string.

func JsonNotContains

func JsonNotContains(t TestingTB, data string, pathexpr string, extra ...any) any

JsonNotContains is the inversion of JsonContains. This fatals the test if the provided JSON path expression matches anything in data.

func MustParseJson

func MustParseJson[T any](t TestingTB, in io.Reader, extra ...any) T

MustParseJson will fatal the test if in cannot be decoded. Returns the decoded item.

func Server

func Server(t TestingTB, handler http.Handler) *httptest.Server

Server starts and returns a new httptest.Server which will shutdown with the test.

Types

type HttpExpectation

type HttpExpectation struct {
	// contains filtered or unexported fields
}

HttpExpectation defines what we expect to receive after sending an HttpTesterRequest, plus any data we want to pull out of it.

func (*HttpExpectation) Test

func (h *HttpExpectation) Test(extra ...any) (captures map[string]string)

Test executes the associated request, failing if expectations are not met, else applies any captures.

type HttpTester

type HttpTester struct {
	// contains filtered or unexported fields
}

HttpTester offers a convenient API for high-level HTTP testing. Use New to get one.

Example
package main

import (
	"encoding/json"
	"fmt"
	"github.com/vaeryn-uk/go-httptester"
	"net/http"
)

var exampleJson = []map[string]any{
	{
		"name": "Scotty",
		"address": map[string]any{
			"number": "123",
			"street": "Fake Street",
			"city":   "Cloud City",
			"zip":    "71622",
		},
	},
}

func main() {
	// For this example, our handler always returns some static JSON.
	handler := exampleHttpHandler()

	// In your tests, t will be given to you. For this example, this just prints
	// any test failures.
	t := &exampleTestRunner{}

	// Initialises an HTTP server via httptest.NewServer that will automatically close
	// when the test is ended.
	srv := httptester.Server(t, handler)

	// Create a new tester to assert against it.
	ht := httptester.New(t, srv)

	ht.Request(
		"GET",
		"/",
		// Configure the request here, e.g. adding a bearer token.
		ht.Bearer("some access token"),
		// Or a JSON body.
		ht.JsonBody("some data"),
	).Expect(
		// Use ExpectXXX() to make assertions against it.
		// Like its return code.
		ht.ExpectCode(200),
		// Or that the raw body contains some string.
		ht.ExpectBodyContains("Fake Street"),
		// Or that a jsonpath expression exists
		ht.ExpectJsonExists("$[0].name"),
		// Or that a jsonpath expression resolve to some specific value
		ht.ExpectJsonMatchStr("$[0].name", "Scotty"),
	).
		// Finally invoke Test() to perform the test.
		Test("optional additional info here will be printed on test failure")

	// You can also use captures to extract JSON values from the response.
	ht = httptester.New(t, srv)
	captures := ht.Request("GET", "/").
		Expect(ht.CaptureJson("street", "$[0].address.street")).
		Test()

	// Now we can use the captured value "street" for other things.
	fmt.Println("captured value:", captures["street"])

	// An example of a failing test.
	ht = httptester.New(t, srv)
	ht.Request("GET", "/").Expect(ht.ExpectJsonExists("$[0].foo")).Test()

}

// exampleHttpHandler creates a test handler to demonstrate the httptester API.
// This just always replies with some JSON.
func exampleHttpHandler() http.Handler {
	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		out, _ := json.Marshal(exampleJson)

		writer.Header().Set("Content-Type", "application/json")
		_, _ = writer.Write(out)
	})
}

// used to satisfy a testing.TB within our example code.
type exampleTestRunner struct {
	finished bool
}

func (e *exampleTestRunner) Cleanup(f func()) {
	// Nothing to do.
}

func (e *exampleTestRunner) Helper() {
	// Nothing to do.
}

func (e *exampleTestRunner) Fatal(args ...any) {
	if !e.finished {
		args = append([]any{"TEST FATAL:"}, args...)
		fmt.Println(args...)
		e.finished = true
	}
}

func (e *exampleTestRunner) Log(args ...any) {
	fmt.Println(args...)
}
Output:

captured value: Fake Street
TEST FATAL: unknown key foo
failed to capture JSON path
$[0].foo
full data
[{"address":{"city":"Cloud City","number":"123","street":"Fake Street","zip":"71622"},"name":"Scotty"}]

func New

func New(t TestingTB, srv *httptest.Server) *HttpTester

New creates a new HttpTester wrapping t and using srv. Common usage:

ht := NewHttpTester(t, srv)
ht.Request("GET", "/api/test", ht.SomeOption(), ...).Expect(ht.SomeExpectation(), ...).Test()

func (*HttpTester) Bearer

func (h *HttpTester) Bearer(token string) RequestOption

Bearer configures a HttpTesterRequest with an bearer authorization token.

func (*HttpTester) Body

func (h *HttpTester) Body(body any, args ...any) RequestOption

Body configures a HttpTesterRequest with some data. If body is an io.Reader, will grab the string data from that. Will fail the test if given something other than a string or reader.

As a convenience, args will be applied to the body via fmt.Printf rules.

func (*HttpTester) CaptureJson

func (h *HttpTester) CaptureJson(name, jsonpath string) ResponseOption

CaptureJson defines a capture against the response's JSON body. If successful, this capture is available under name from HttpExpectation.Test. Will fatal if there are no string value to capture, so this implies ExpectJsonExists.

func (*HttpTester) ExpectBodyContains

func (h *HttpTester) ExpectBodyContains(content string) ResponseOption

ExpectBodyContains configure an HttpExpectation to require the response body contains the content string at least once.

func (*HttpTester) ExpectCode

func (h *HttpTester) ExpectCode(code int) ResponseOption

ExpectCode configures an HttpExpectation to require a certain response code.

func (*HttpTester) ExpectContentType

func (h *HttpTester) ExpectContentType(contentType string) ResponseOption

func (*HttpTester) ExpectJsonExists

func (h *HttpTester) ExpectJsonExists(path string) ResponseOption

ExpectJsonExists configures an HttpExpectation to require a JSON body which contains a non-empty string value at jsonpath path.

func (*HttpTester) ExpectJsonMatch

func (h *HttpTester) ExpectJsonMatch(path string, match any) ResponseOption

ExpectJsonMatch asserts that the HTTP response has a JSON body which contains a value at JSON path which matches parameter match.

Note that numbers in the JSON will be float64 in Go.

func (*HttpTester) ExpectJsonMatchStr

func (h *HttpTester) ExpectJsonMatchStr(path, match string) ResponseOption

ExpectJsonMatchStr extends ExpectJsonExists to also ensure that the value found at jsonpath path matches the expected string match.

func (*HttpTester) ExpectJsonNotExists

func (h *HttpTester) ExpectJsonNotExists(path string) ResponseOption

func (*HttpTester) ExpectYamlMatch

func (h *HttpTester) ExpectYamlMatch(path string, match any) ResponseOption

ExpectYamlMatch asserts that the HTTP response has a YAML body which contains a value at JSON path which matches the parameter match.

This convert YAML to JSON, then performs matching as per JSONPath.

This makes no assertions on the response's content type, but will fail the test if the content cannot be parsed as YAML.

Note that numbers in the JSON will be float64 in Go.

func (*HttpTester) Header

func (h *HttpTester) Header(name, val string) RequestOption

Header configures a HttpTesterRequest to have a have with the given name, set to the given val. E.g.:

ht.Header("Content-Type", "text/plain")

func (*HttpTester) JsonBody

func (h *HttpTester) JsonBody(body any, args ...any) RequestOption

JsonBody configures a HttpTesterRequest with a JSON body. If body is an io.Reader, will grab the string data from that. If it is any other non-string body, we json.Unmarshal it to get a string.

As a convenience, args will be applied to the JSONified data via fmt.Printf rules after the JSON is generated.

func (*HttpTester) MultipartFormField

func (h *HttpTester) MultipartFormField(name string, val []byte) RequestOption

func (*HttpTester) MultipartFormFile

func (h *HttpTester) MultipartFormFile(fieldname, filename string, data io.Reader) RequestOption

func (*HttpTester) Request

func (h *HttpTester) Request(method, path string, options ...RequestOption) *HttpTesterRequest

Request creates a configured HttpTesterRequest. Forgetting to call Expect().Test() on this request will cause a failure in the test.

func (*HttpTester) YamlBody

func (h *HttpTester) YamlBody(body any, args ...any) RequestOption

YamlBody configures a HttpTesterRequest with a YAML body. If body is an io.Reader, will grab the string data from that. If it is any other non-string body, we yaml.Unmarshal it to get a string.

As a convenience, args will be applied to the YAMLified data via fmt.Printf rules after the YAML is generated.

"application/x-yaml" is set as the request content type.

type HttpTesterRequest

type HttpTesterRequest struct {
	// contains filtered or unexported fields
}

HttpTesterRequest defines a request we're going to test against.

func (*HttpTesterRequest) Expect

func (h *HttpTesterRequest) Expect(options ...ResponseOption) *HttpExpectation

Expect returns a configured HttpExpectation to test against.

type RequestOption

type RequestOption func(req *HttpTesterRequest)

RequestOption is used to configure an HttpTesterRequest.

type ResponseOption

type ResponseOption func(expectation *HttpExpectation)

ResponseOption is used to configure an HttpExpectation.

type TestingTB

type TestingTB interface {
	Cleanup(func())
	Helper()
	Fatal(args ...any)
	Log(arg ...any)
}

TestingTB is a subset of testing.TB. This is here to allow for example code, but in real tests, this anything that accepts TestingTB should be given a testing.TB.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL