forest

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 2, 2018 License: MIT Imports: 16 Imported by: 0

README

forest

forest - for testing REST api-s in Go

Build Status Go Report Card GoDoc

This package provides a few simple helper types and functions to create functional tests that call a running REST based WebService.

Introduction Blog Post

© 2016, http://ernestmicklei.com. MIT License. Contributions welcome.

forest

Documentation

Overview

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

Constants

This section is empty.

Variables

View Source
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.

View Source
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.

View Source
var LoggingPrintf = fmt.Printf

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

View Source
var Path = NewConfig

Path is an alias for NewConfig

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

Functions

func CheckError

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

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

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

Errorf calls Error on t with a colorized message

func ExpectHeader

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

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

Example
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")
Output:

func ExpectJSONArray

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.

Example
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.
})
Output:

func ExpectJSONDocument

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

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.

Example
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.
})
Output:

func ExpectStatus

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.

Example
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)
Output:

func ExpectString

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

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.

Example

How to use the ExpectXMLDocument function on a http response.

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.
Output:

func Fatalf

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

Fatalf calls Fatal on t with a colorized message

func JSONArrayPath

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"} ]

Example
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)
}
Output:

func JSONPath

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"}] }

Example
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)
}
Output:

func Logf

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

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

func ProcessTemplate

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

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

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
Example
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
Output:

func URLPathEncode

func URLPathEncode(path string) string

func VerboseOnFailure

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

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

Example
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)
}
Output:

Types

type APITesting

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

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

func NewClient

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

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

func (*APITesting) DELETE

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

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

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

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

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

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

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

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

Error is equivalent to Log followed by Fail.

func (Logger) Fail

func (l Logger) Fail()

Fail marks the function as having failed but continues execution.

func (Logger) FailNow

func (l Logger) FailNow()

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

func (Logger) Fatal

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

Fatal is equivalent to Log followed by FailNow.

func (Logger) Logf

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

type RequestConfig struct {
	URI        string
	BodyReader io.Reader
	HeaderMap  http.Header
	// for Query parameters
	Values         url.Values
	FormData       url.Values
	User, Password string
}

RequestConfig holds additional information to construct a Http request.

Example
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")
Output:

func NewConfig

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

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

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

func (*RequestConfig) Body

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

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

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

func (r *RequestConfig) Form(bodyData url.Values) *RequestConfig

Form set the FormData values e.g for POST operation.

func (*RequestConfig) Header

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

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

func (*RequestConfig) Path

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

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

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

func (*RequestConfig) Read

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

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

type T

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL