Testing your REST api in Go with forest

Go package forest was created to simplify the code needed to write real tests of a REST api. Such tests need to setup Http Requests, send them and inspect Http Responses. The Go standard library has all that is needed to achieve this flow but the required amount of code, especially the error handling, makes tests harder to read and longer to write.

forestview

Testing github

Let’s create a few tests that examine the response of the public github API call.


package main

import (
    "net/http"
    "testing"

    . "github.com/emicklei/forest"
)

var github = NewClient("https://api.github.com", new(http.Client))

func TestForestProjectExists(t *testing.T) {
    cfg := NewConfig("/repos/emicklei/{repo}", "forest").Header("Accept", "application/json")
    r := github.GET(t, cfg)
    ExpectStatus(t, r, 200)
}

The package variable github (line 10) is a forest Http client wrapper and provides the api to send Http requests (such as GET). On line 13, the cfg variable is a RequestConfig value that is used to build a http.Request with method,url,headers,body. The variable r is a *http.Response. The ExpectStatus function checks the status code. If the code does not match, then the following will be reported.

forest_github_test1

When it comes to verifying the response payload, the forest package offers several functions to inspect JSON and XML documents. All functions operate on the *http.Response and take the *testing.T for reporting errors.

Now we extend the test to inspect the response document. Follow https://api.github.com/repos/emicklei/forest to see the document in your browser.


    type Repository struct {
        Owner struct {
            HtmlUrl string `json:"html_url"`
        } `json:"owner"`
    }
    var repo Repository

    ExpectJSONDocument(t, r, &repo)

    if got, want := repo.Owner.HtmlUrl, "https://github.com/emicklei"; got != want {
        t.Errorf("got %v want %v", got, want)
    }

Line 24 uses the ExpectJSONDocument function that takes the Http response r and tries to unmarshal the response content into a struct.

If only you need to inspect a specific document element, use the JSONPath function instead.


    if got, want := JSONPath(t, r, ".owner.html_url"), "https://github.com/emicklei"; got != want {
        t.Errorf("got %v want %v", got, want)
    }

Other functions exist in forest that marshal documents into structs, use templating to create Request bodies, navigate XML or JSON document using XPath and JSONPath. In contrast to the standard behavior, the Body of a http.Response is made re-readable. This means one can apply multiple expectations to a response as well as dump the full contents.


func ExpectHeader(t T, r *http.Response, name, value string)
func ExpectJSONArray(t T, r *http.Response, callback func(array []interface{}))
func ExpectJSONDocument(t T, r *http.Response, doc interface{})
func ExpectJSONHash(t T, r *http.Response, callback func(hash map[string]interface{}))
func ExpectStatus(t T, r *http.Response, status int) bool
func ExpectString(t T, r *http.Response, callback func(content string))
func ExpectXMLDocument(t T, r *http.Response, doc interface{})
func JSONArrayPath(t T, r *http.Response, dottedPath string) interface{}
func JSONPath(t T, r *http.Response, dottedPath string) interface{}
func ProcessTemplate(t T, templateContent string, value interface{}) string
func Scolorf(syntaxCode string, format string, args ...interface{}) string
func SkipUnless(s skippeable, labels ...string)
func XMLPath(t T, r *http.Response, xpath string) interface{}
func Dump(t T, resp *http.Response)

Testimony

In our company, many teams have build microservices and created all kinds of tests for them using different tools (jmeter,fitnesse,gatling,…) and frameworks (JUnit,Spec,Cucumber,…). For the services in our team, we started out with JMeter as we needed performance tests. We did write full API tests in JMeter as well, but soon we realized that we could write and run tests faster in Go, the language we also use to implement the services. Abstractions with types and functions really helps to write a large collection of maintainable tests. And because of the speed of compilation and execution, we now run these API tests as frequently as we run the unit tests of our services.

Get started

See the full documentation on Godoc. This open-source project is available at https://github.com/emicklei/forest. MIT License

comments powered by Disqus