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.
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.
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)