apitest

by steinfletcher

steinfletcher / apitest

A simple and extensible behavioural testing library for Go. You can use api test to simplify REST AP...

245 Stars 22 Forks Last release: 28 days ago (v1.4.11) MIT License 263 Commits 74 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

Godoc Coverage Status Build Status Go Report Card Mentioned in Awesome Go

apitest

A simple and extensible behavioural testing library. 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.

Join the conversation at #apitest on https://gophers.slack.com.

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 | | graphql | using gqlgen.com to generate a graphql server | | gorilla | the gorilla web toolkit | | iris | iris web framework | | echo | High performance, extensible, minimalist Go web framework | | fiber | Express inspired web framework written in Go | | httprouter | High performance HTTP request router that scales well | | 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 | | CSS Selectors | CSS selector assertion addons | | PlantUML | Export sequence diagrams as plantUML | | DynamoDB | Add DynamoDB interactions to sequence diagrams |

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.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.