Skip to content
A simple and extensible behavioural testing library written in golang. You can use api test to simplify REST API, HTTP handler and e2e tests.
Go Makefile
Branch: master
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
docs Add more test scenarios for mock matchers. Also add more documentatio… Aug 2, 2019
examples Fix up goreportcard issues Jul 29, 2019
mocks fail test if mock.Times is configured and mock is not invoked Aug 11, 2019
testdata trim host from diagram lines Jul 3, 2019
x/db Fix up goreportcard issues Jul 29, 2019
.codacy.yml exclude examples from static analysis Jan 15, 2019
.gitignore update report docs Jun 2, 2019
.travis.yml Add duration to debug output Jun 23, 2019
CONTRIBUTING.md improve docs Jan 16, 2019
LICENSE Add license Dec 30, 2018
Makefile Pass error message to assertion library Aug 11, 2019
README.md Add credits to README Jul 30, 2019
apitest.go fail test if mock.Times is configured and mock is not invoked Aug 11, 2019
apitest_test.go fail test if mock.Times is configured and mock is not invoked Aug 11, 2019
assert.go Pass error message to assertion library Aug 11, 2019
assert_test.go normalize http headers in mocks Jan 17, 2019
cookies.go Fix up goreportcard issues Jul 29, 2019
cookies_test.go add cookie matchers to mocks Apr 26, 2019
diagram.go Fix up body copying logic. Jul 29, 2019
diagram_test.go Fix up body copying logic. Jul 29, 2019
go.mod Add stretchr testify for better JSON assertions and output Feb 20, 2019
go.sum Add stretchr testify for better JSON assertions and output Feb 20, 2019
mocks.go fail test if mock.Times is configured and mock is not invoked Aug 11, 2019
mocks_test.go Turn off debug logging in tests Aug 11, 2019
report.go Report formatter should be public since it is depended upon by external Jul 28, 2019
report_test.go Add a database driver wrapper to record DB events in sequence diagrams May 14, 2019
template.go Mainly comments to get gofmt errors down. Jul 28, 2019

README.md

Godoc Coverage Status Build Status Go Report Card

apitest

A simple and extensible behavioural testing library in golang. Supports mocking external http calls and renders sequence diagrams on completion.

In behavioural tests the internal structure of the app is not known by the tests. Data is input to the system and the outputs are expected to meet certain conditions.

Logo by @egonelbre

Documentation

Please visit https://apitest.dev for the latest documentation.

Installation

go get -u github.com/steinfletcher/apitest

Examples

Framework and library integration examples

Example Comment
gin popular martini-like web framework
gorilla the gorilla web toolkit
iris iris web framework
echo High performance, extensible, minimalist Go web framework
mocks example mocking out external http calls
sequence diagrams generate sequence diagrams from tests. See the demo

Companion libraries

Library Comment
JSONPath JSONPath assertion addons
PlantUML Export sequence diagrams as plantUML

Credits

This library was influenced by the following software packages:

  • YatSpec for creating sequence diagrams from tests
  • MockMVC and superagent for the concept and behavioural testing approach
  • Gock for the approach to mocking HTTP services in Go
  • Baloo for API design

Credit to testify which is this libraries' only dependency.

Code snippets

JSON body matcher

func TestApi(t *testing.T) {
	apitest.New().
		Handler(handler).
		Get("/user/1234").
		Expect(t).
		Body(`{"id": "1234", "name": "Tate"}`).
		Status(http.StatusCreated).
		End()
}

JSONPath

For asserting on parts of the response body JSONPath may be used. A separate module must be installed which provides these assertions - go get -u github.com/steinfletcher/apitest-jsonpath. This is packaged separately to keep this library dependency free.

Given the response is {"a": 12345, "b": [{"key": "c", "value": "result"}]}

func TestApi(t *testing.T) {
	apitest.New().
		Handler(handler).
		Get("/hello").
		Expect(t).
		Assert(jsonpath.Contains(`$.b[? @.key=="c"].value`, "result")).
		End()
}

and jsonpath.Equals checks for value equality

func TestApi(t *testing.T) {
	apitest.New(handler).
		Get("/hello").
		Expect(t).
		Assert(jsonpath.Equal(`$.a`, float64(12345))).
		End()
}

Custom assert functions

func TestApi(t *testing.T) {
	apitest.New().
		Handler(handler).
		Get("/hello").
		Expect(t).
		Assert(func(res *http.Response, req *http.Request) {
			assert.Equal(t, http.StatusOK, res.StatusCode)
		}).
		End()
}

Assert cookies

func TestApi(t *testing.T) {
	apitest.New().
		Handler(handler).
		Patch("/hello").
		Expect(t).
		Status(http.StatusOK).
		Cookies(apitest.Cookie"ABC").Value("12345")).
		CookiePresent("Session-Token").
		CookieNotPresent("XXX").
		Cookies(
			apitest.Cookie("ABC").Value("12345"),
			apitest.Cookie("DEF").Value("67890"),
		).
		End()
}

Assert headers

func TestApi(t *testing.T) {
	apitest.New().
		Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		Headers(map[string]string{"ABC": "12345"}).
		End()
}

Mocking external http calls

var getUser = apitest.NewMock().
	Get("/user/12345").
	RespondWith().
	Body(`{"name": "jon", "id": "1234"}`).
	Status(http.StatusOK).
	End()

var getPreferences = apitest.NewMock().
	Get("/preferences/12345").
	RespondWith().
	Body(`{"is_contactable": true}`).
	Status(http.StatusOK).
	End()

func TestApi(t *testing.T) {
	apitest.New().
		Mocks(getUser, getPreferences).
		Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		Body(`{"name": "jon", "id": "1234"}`).
		End()
}

Generating sequence diagrams from tests

func TestApi(t *testing.T) {
	apitest.New().
		Report(apitest.SequenceDiagram()).
		Mocks(getUser, getPreferences).
		Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		Body(`{"name": "jon", "id": "1234"}`).
		End()
}

It is possible to override the default storage location by passing the formatter instance Report(apitest.NewSequenceDiagramFormatter(".sequence-diagrams")). You can bring your own formatter too if you want to produce custom output. By default a sequence diagram is rendered on a html page. See the demo

Debugging http requests and responses generated by api test and any mocks

func TestApi(t *testing.T) {
	apitest.New().
		Debug().
		Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide basic auth in the request

func TestApi(t *testing.T) {
	apitest.New().
		Handler(handler).
		Get("/hello").
		BasicAuth("username:password").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide cookies in the request

func TestApi(t *testing.T) {
	apitest.New().
		Handler(handler).
		Get("/hello").
		Cookies(apitest.Cookie("ABC").Value("12345")).
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide headers in the request

func TestApi(t *testing.T) {
	apitest.New().
		Handler(handler).
		Delete("/hello").
		Headers(map[string]string{"My-Header": "12345"}).
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide query parameters in the request

Query, QueryParams and QueryCollection can all be used in combination

func TestApi(t *testing.T) {
	apitest.New().
		Handler(handler).
		Get("/hello").
		QueryParams(map[string]string{"a": "1", "b": "2"}).
		Query("c", "d").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Providing {"a": {"b", "c", "d"} results in parameters encoded as a=b&a=c&a=d. QueryCollection can be used in combination with Query

func TestApi(t *testing.T) {
	apitest.New().
		Handler(handler).
		Get("/hello").
		QueryCollection(map[string][]string{"a": {"b", "c", "d"}}).
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide a url encoded form body in the request

func TestApi(t *testing.T) {
	apitest.New().
		Handler(handler).
		Post("/hello").
		FormData("a", "1").
		FormData("b", "2").
		FormData("b", "3").
		FormData("c", "4", "5", "6").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Capture the request and response data

func TestApi(t *testing.T) {
	apitest.New().
		Observe(func(res *http.Response, req *http.Request, apiTest *apitest.APITest) {
			// do something with res and req
		}).
		Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Intercept the request

This is useful for mutating the request before it is sent to the system under test.

func TestApi(t *testing.T) {
	apitest.New().
		Handler(handler).
		Intercept(func(req *http.Request) {
			req.URL.RawQuery = "a[]=xxx&a[]=yyy"
		}).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Contributing

View the contributing guide.

You can’t perform that action at this time.