forest: github.com/emicklei/forest Index | Examples | Files

package forest

import "github.com/emicklei/forest"

Package forest has functions for REST Api testing in Go

This package provides a few simple helper types and functions to create functional tests that call HTTP services. A test uses a forest Client which encapsulates a standard http.Client and a base url. Such a client can be created inside a function or by initializing a package variable for shared access. Using a client, you can send http requests and call multiple expectation functions on each response.

Most functions of the forest package take the *testing.T variable as an argument to report any error.

Example

// setup a shared client to your API
var chatter = forest.NewClient("http://api.chatter.com", new(http.Client))

func TestGetMessages(t *testing.T) {
	r := chatter.GET(t, forest.Path("/v1/messages").Query("user","zeus"))
	ExpectStatus(t,r,200)
	ExpectJSONArray(t,r,func(messages []interface{}){

		// in the callback you can validate the response structure
		if len(messages) == 0 {
			t.Error("expected messages, got none")
		}
	})
}

To compose http requests, you create a RequestConfig value which as a Builder interface for setting the path,query,header and body parameters. The ProcessTemplate function can be useful to create textual payloads. To inspect http responses, you use the Expect functions that perform the unmarshalling or use XMLPath or JSONPath functions directly on the response.

If needed, implement the standard TestMain to do global setup and teardown.

func TestMain(m *testing.M) {
	// there is no *testing.T available, use an stdout implementation
	t := forest.TestingT

	// setup
	chatter.PUT(t, forest.Path("/v1/messages/{id}",1).Body("<payload>"))
	ExpectStatus(t,r,204)

	exitCode := m.Run()

	// teardown
	chatter.DELETE(t, forest.Path("/v1/messages/{id}",1))
	ExpectStatus(t,r,204)

	os.Exit(exitCode)
}

Special features

In contrast to the standard behavior, the Body of a http.Response is made re-readable. This means one can apply expectations to a response as well as dump the full contents.

The function XMLPath provides XPath expression support. It uses the [https://godoc.org/launchpad.net/xmlpath] package. The similar function JSONPath can be used on JSON documents.

Colorizes error output (can be configured using package vars).

All functions can also be used in a setup and teardown as part of TestMain.

(c) 2015, http://ernestmicklei.com. MIT License

Index

Examples

Package Files

api_testing.go doc.go error_color.go expectations.go helpers.go jsonpath.go log.go logger.go request_config.go std_testing.go template.go xmlpath.go

Variables

var ErrorColorSyntaxCode = "@{wR}"

ErrorColorSyntaxCode requires the syntax defined on https://github.com/wsxiaoys/terminal/blob/master/color/color.go . Set to an empty string to disable coloring.

var FatalColorSyntaxCode = "@{wR}"

FatalColorSyntaxCode requires the syntax defined on https://github.com/wsxiaoys/terminal/blob/master/color/color.go . Set to an empty string to disable coloring.

var LoggingPrintf = fmt.Printf

LoggingPrintf is the function used by TestingT to produce logging on Logf,Error and Fatal.

var Path = NewConfig

Path is an alias for NewConfig

var TerminalColorsEnabled = true

TerminalColorsEnabled can be changed to disable the use of terminal coloring. One usecase is to add a command line flag to your test that controls its value.

func init() {
	flag.BoolVar(&forest.TerminalColorsEnabled, "color", true, "want colors?")
}

go test -color=false
var TestingT = Logger{InfoEnabled: true, ErrorEnabled: true, ExitOnFatal: true}

TestingT provides a sub-api of testing.T. Its purpose is to allow the use of this package in TestMain(m).

func CheckError Uses

func CheckError(t T, err error) bool

CheckError simply tests the error and fail is not undefined. This is implicity called after sending a Http request. Return true if there was an error.

func Dump Uses

func Dump(t T, resp *http.Response)

Dump is a convenient method to log the full contents of a request and its response.

func Errorf Uses

func Errorf(t *testing.T, format string, args ...interface{})

Errorf calls Error on t with a colorized message

func ExpectHeader Uses

func ExpectHeader(t T, r *http.Response, name, value string) bool

ExpectHeader inspects the header of the response. Return true if the header matches.

Code:

var t *testing.T

yourAPI := NewClient("http://api.yourservices.com", new(http.Client)) // yourAPI could be a package variable

r := yourAPI.GET(t, Path("/v1/assets/artreyu").Header("Accept", "application/xml"))
ExpectHeader(t, r, "Content-Type", "application/xml")

func ExpectJSONArray Uses

func ExpectJSONArray(t T, r *http.Response, callback func(array []interface{})) bool

ExpectJSONArray tries to unmarshal the response body into a Go slice callback parameter. Fail if the body could not be read or if unmarshalling was not possible. Returns true if the callback was executed with an array.

Code:

var t *testing.T

yourAPI := NewClient("http://api.yourservices.com", new(http.Client)) // yourAPI could be a package variable

r := yourAPI.GET(t, Path("/v1/assets").Header("Content-Type", "application/json"))
ExpectJSONArray(t, r, func(array []interface{}) {
    // here you should inspect the array for expected content
    // and use t (*testing.T) to report a failure.
})

func ExpectJSONDocument Uses

func ExpectJSONDocument(t T, r *http.Response, doc interface{}) bool

ExpectJSONDocument tries to unmarshal the response body into fields of the provided document (struct). Fail if the body could not be read or unmarshalled. Returns true if a document could be unmarshalled.

func ExpectJSONHash Uses

func ExpectJSONHash(t T, r *http.Response, callback func(hash map[string]interface{})) bool

ExpectJSONHash tries to unmarshal the response body into a Go map callback parameter. Fail if the body could not be read or if unmarshalling was not possible. Returns true if the callback was executed with a map.

Code:

var t *testing.T

yourAPI := NewClient("http://api.yourservices.com", new(http.Client)) // yourAPI could be a package variable

r := yourAPI.GET(t, Path("/v1/assets/artreyu/descriptor").Header("Content-Type", "application/json"))
ExpectJSONHash(t, r, func(hash map[string]interface{}) {
    // here you should inspect the hash for expected content
    // and use t (*testing.T) to report a failure.
})

func ExpectStatus Uses

func ExpectStatus(t T, r *http.Response, status int) bool

ExpectStatus inspects the response status code. If the value is not expected, the complete request, response is logged (iff verboseOnFailure) and the test is aborted. Return true if the status is as expected.

Code:

var t *testing.T

yourAPI := NewClient("http://api.yourservices.com", new(http.Client)) // yourAPI could be a package variable

r := yourAPI.GET(t, Path("/v1/assets/artreyu").Header("Accept", "application/xml"))
ExpectStatus(t, r, 200)

func ExpectString Uses

func ExpectString(t T, r *http.Response, callback func(content string)) bool

ExpectString reads the response body into a Go string callback parameter. Fail if the body could not be read or unmarshalled. Returns true if a response body was read.

func ExpectXMLDocument Uses

func ExpectXMLDocument(t T, r *http.Response, doc interface{}) bool

ExpectXMLDocument tries to unmarshal the response body into fields of the provided document (struct). Fail if the body could not be read or unmarshalled. Returns true if a document could be unmarshalled.

How to use the ExpectXMLDocument function on a http response.

Code:

var t *testing.T

yourAPI := NewClient("http://api.yourservices.com", new(http.Client)) // yourAPI could be a package variable

r := yourAPI.GET(t, Path("/v1/assets").Header("Accept", "application/xml"))

var root YourType // YourType must reflect the expected document structure
ExpectXMLDocument(t, r, &root)
// here you should inspect the root (instance of YourType) for expected field values
// and use t (*testing.T) to report a failure.

func Fatalf Uses

func Fatalf(t *testing.T, format string, args ...interface{})

Fatalf calls Fatal on t with a colorized message

func JSONArrayPath Uses

func JSONArrayPath(t T, r *http.Response, dottedPath string) interface{}

JSONArrayPath returns the value found by following the dotted path in a JSON array. E.g .1.title in [ {"title":"Go a long way"}, {"title":"scary scala"} ]

Code:

var t *testing.T

yourAPI := NewClient("http://api.yourservices.com", new(http.Client)) // yourAPI could be a package variable

r := yourAPI.GET(t, Path("/v1/assets").Header("Content-Type", "application/json"))
// if the content looks like this
// [
// { "id" : "artreyu", "type" : "tool" }
// ]
// then you can verify it using
if got, want := JSONArrayPath(t, r, ".0.type"), "tool"; got != want {
    t.Errorf("got %v want %v", got, want)
}

func JSONPath Uses

func JSONPath(t T, r *http.Response, dottedPath string) interface{}

JSONPath returns the value found by following the dotted path in a JSON document hash. E.g .chapters.0.title in { "chapters" : [{"title":"Go a long way"}] }

Code:

var t *testing.T

yourAPI := NewClient("http://api.yourservices.com", new(http.Client)) // yourAPI could be a package variable

r := yourAPI.GET(t, Path("/v1/assets/artreyu").Header("Content-Type", "application/json"))
// if the content looks like this
// { "id" : "artreyu", "type" : "tool" }
// then you can verify it using
if got, want := JSONPath(t, r, ".0.id"), "artreyu"; got != want {
    t.Errorf("got %v want %v", got, want)
}

func Logf Uses

func Logf(t T, format string, args ...interface{})

Logf adds the actual file:line information to the log message

func ProcessTemplate Uses

func ProcessTemplate(t T, templateContent string, value interface{}) string

ProcessTemplate creates a new text Template and executes it using the provided value. Returns the string result of applying this template. Failures in the template are reported using t.

func Scolorf Uses

func Scolorf(syntaxCode string, format string, args ...interface{}) string

Scolorf returns a string colorized for terminal output using the syntaxCode (unless that's empty). Requires the syntax defined on https://github.com/wsxiaoys/terminal/blob/master/color/color.go .

func SkipUnless Uses

func SkipUnless(t skippeable, labels ...string)

SkipUnless will Skip the test unless the LABELS environment variable includes any of the provided labels.

LABELS=integration,nightly go test -v

Code:

var t *testing.T

// t implements skippeable (has method Skipf)
SkipUnless(t, "nightly")
// code below is executed only if the environment variable LABELS contains `nightly`

// You run the `nightly` tests like this:
//
// LABELS=nightly go test -v

func URLPathEncode Uses

func URLPathEncode(path string) string

func VerboseOnFailure Uses

func VerboseOnFailure(verbose bool)

VerboseOnFailure (default is false) will produce more information about the request and response when a failure is detected on an expectation. This setting is not the same but related to the value of testing.Verbose().

func XMLPath Uses

func XMLPath(t T, r *http.Response, xpath string) interface{}

XMLPath returns the value found by following the xpath expression in a XML document (payload of response).

Code:

var t *testing.T

yourAPI := NewClient("http://api.yourservices.com", new(http.Client)) // yourAPI could be a package variable

r := yourAPI.GET(t, Path("/v1/assets/artreyu").Header("Accept", "application/xml"))
// if the content looks like this
// <?xml version="1.0" ?>
// <asset>
//   <id>artreyu</id>
//   <type>tool</type>
// </asset>
// then you can verify it using
if got, want := XMLPath(t, r, "/asset/id"), "artreyu"; got != want {
    t.Errorf("got %v want %v", got, want)
}

type APITesting Uses

type APITesting struct {
    BaseURL string
    // contains filtered or unexported fields
}

APITesting provides functions to call a REST api and validate its responses.

func NewClient Uses

func NewClient(baseURL string, httpClient *http.Client) *APITesting

NewClient returns a new ApiTesting that can be used to send Http requests.

func (*APITesting) DELETE Uses

func (a *APITesting) DELETE(t T, config *RequestConfig) *http.Response

DELETE sends a Http request using a config (headers,...) The request is logged and any sending error will fail the test.

func (*APITesting) Do Uses

func (a *APITesting) Do(method string, config *RequestConfig) (*http.Response, error)

Do sends a Http request using a Http method (GET,PUT,POST,....) and config (headers,...) The request is not logged and any URL build error or send error will be returned.

func (*APITesting) GET Uses

func (a *APITesting) GET(t T, config *RequestConfig) *http.Response

GET sends a Http request using a config (headers,...) The request is logged and any sending error will fail the test.

func (*APITesting) PATCH Uses

func (a *APITesting) PATCH(t T, config *RequestConfig) *http.Response

PATCH sends a Http request using a config (headers,...) The request is logged and any sending error will fail the test.

func (*APITesting) POST Uses

func (a *APITesting) POST(t T, config *RequestConfig) *http.Response

POST sends a Http request using a config (headers,body,...) The request is logged and any sending error will fail the test.

func (*APITesting) PUT Uses

func (a *APITesting) PUT(t T, config *RequestConfig) *http.Response

PUT sends a Http request using a config (headers,body,...) The request is logged and any sending error will fail the test.

type Logger Uses

type Logger struct {
    InfoEnabled  bool
    ErrorEnabled bool
    ExitOnFatal  bool
}

Logger can be used for the testing.T parameter for forest functions when you need more control over what to log and how to handle fatals. The variable TestingT is a Logger with all enabled.

func (Logger) Error Uses

func (l Logger) Error(args ...interface{})

Error is equivalent to Log followed by Fail.

func (Logger) Fail Uses

func (l Logger) Fail()

Fail marks the function as having failed but continues execution.

func (Logger) FailNow Uses

func (l Logger) FailNow()

FailNow marks the function as having failed and stops its execution.

func (Logger) Fatal Uses

func (l Logger) Fatal(args ...interface{})

Fatal is equivalent to Log followed by FailNow.

func (Logger) Logf Uses

func (l Logger) Logf(format string, args ...interface{})

Logf formats its arguments according to the format, analogous to Printf, and records the text in the error log. The text will be printed only if the test fails or the -test.v flag is set.

type RequestConfig Uses

type RequestConfig struct {
    URI            string
    BodyReader     io.Reader
    HeaderMap      http.Header
    Values         url.Values
    User, Password string
}

RequestConfig holds additional information to construct a Http request.

Code:

var cfg *RequestConfig

// set path template and header
cfg = Path("/v1/assets/{id}", "artreyu").
    Header("Accept", "application/json")

// set query parameters (the config will do escaping)
cfg = NewConfig("/v1/assets").
    Query("lang", "en")

// contents as is
cfg = Path("/v1/assets").
    Body("some payload for POST or PUT")

// content from file (io.Reader)
cfg = Path("/v1/assets")
f, _ := os.Open("payload.xml")
cfg.BodyReader = f

// content by marshalling (xml,json,plain text) your value
cfg = NewConfig("/v1/assets").
    Content(time.Now(), "application/json")

func NewConfig Uses

func NewConfig(pathTemplate string, pathParams ...interface{}) *RequestConfig

NewConfig returns a new RequestConfig with initialized empty headers and query parameters. See Path for an explanation of the function parameters.

func (*RequestConfig) BasicAuth Uses

func (r *RequestConfig) BasicAuth(username, password string) *RequestConfig

BasicAuth sets the credentials for Basic Authentication (if username is not empty)

func (*RequestConfig) Body Uses

func (r *RequestConfig) Body(body string) *RequestConfig

Body sets the playload as is. No content type is set. It sets the BodyReader field of the RequestConfig.

func (*RequestConfig) Content Uses

func (r *RequestConfig) Content(payload interface{}, contentType string) *RequestConfig

Content encodes (marshals) the payload conform the content type given. If the payload is already a string (JSON,XML,plain) then it is used as is. Supported Content-Type values for marshalling: application/json, application/xml, text/plain Payload can also be a slice of bytes; use application/octet-stream in that case. It sets the BodyReader field of the RequestConfig.

func (*RequestConfig) Do Uses

func (r *RequestConfig) Do(block func(config *RequestConfig)) *RequestConfig

Do calls the one-argument function parameter with the receiver. This allows for custom convenience functions without breaking the fluent programming style.

func (*RequestConfig) Header Uses

func (r *RequestConfig) Header(name, value string) *RequestConfig

Header adds a name=value pair to the list of header parameters.

func (*RequestConfig) Path Uses

func (r *RequestConfig) Path(pathTemplate string, pathParams ...interface{}) *RequestConfig

Path sets the URL path with optional path parameters. format example: /v1/persons/{param}/ + 42 => /v1/persons/42 format example: /v1/persons/:param/ + 42 => /v1/persons/42 format example: /v1/assets/*rest + js/some/file.js => /v1/assets/js/some/file.js

func (*RequestConfig) Query Uses

func (r *RequestConfig) Query(name string, value interface{}) *RequestConfig

Query adds a name=value pair to the list of query parameters.

func (*RequestConfig) Read Uses

func (r *RequestConfig) Read(bodyReader io.Reader) *RequestConfig

Read sets the BodyReader for content to send with the request.

type T Uses

type T interface {
    // Logf formats its arguments according to the format, analogous to Printf, and records the text in the error log.
    // The text will be printed only if the test fails or the -test.v flag is set.
    Logf(format string, args ...interface{})
    // Error is equivalent to Log followed by Fail.
    Error(args ...interface{})
    // Fatal is equivalent to Log followed by FailNow.
    Fatal(args ...interface{})
    // FailNow marks the function as having failed and stops its execution.
    FailNow()
    // Fail marks the function as having failed but continues execution.
    Fail()
}

T is the interface that this package is using from standard testing.T

Package forest imports 16 packages (graph). Updated 2017-07-12. Refresh now. Tools for package owners.