apitest

package module
v1.0.8 Latest Latest
Warning

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

Go to latest
Published: Feb 20, 2019 License: MIT Imports: 24 Imported by: 16

README

apitest

GoDoc Go Report Card Build Status Coverage Status Buy Me A Coffee donate button

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

Installation

go get -u github.com/steinfletcher/apitest

Examples

Framework and library integration examples
Example Comment
gin popular martini-like web framework
gorilla the gorilla web toolkit
iris iris web framework
echo High performance, extensible, minimalist Go web framework
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
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()
}
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.

Documentation

Index

Constants

View Source
const Template = `` /* 2256-byte string literal not displayed */

Variables

This section is empty.

Functions

This section is empty.

Types

type APITest

type APITest struct {
	// contains filtered or unexported fields
}

APITest is the top level struct holding the test spec

func New

func New(name ...string) *APITest

New creates a new api test. The name is optional and will appear in test reports

func (*APITest) BuildRequest

func (a *APITest) BuildRequest() *http.Request

func (*APITest) Debug

func (a *APITest) Debug() *APITest

Debug logs to the console the http wire representation of all http interactions that are intercepted by apitest. This includes the inbound request to the application under test, the response returned by the application and any interactions that are intercepted by the mock server.

func (*APITest) Delete added in v1.0.3

func (a *APITest) Delete(url string) *Request

Delete is a convenience method for setting the request as http.MethodDelete

func (*APITest) Get added in v1.0.3

func (a *APITest) Get(url string) *Request

Get is a convenience method for setting the request as http.MethodGet

func (*APITest) Handler

func (a *APITest) Handler(handler http.Handler) *APITest

Handler defines the http handler that is invoked when the test is run

func (*APITest) HttpClient

func (a *APITest) HttpClient(cli *http.Client) *APITest

HttpClient allows the developer to provide a custom http client when using mocks

func (*APITest) Intercept added in v1.0.3

func (a *APITest) Intercept(interceptor Intercept) *APITest

Intercept is a builder method for setting the request interceptor

func (*APITest) Meta added in v1.0.2

func (a *APITest) Meta(meta map[string]interface{}) *APITest

Meta provides a hook to add custom meta data to the test which can be picked up when defining a custom reporter

func (*APITest) Method added in v1.0.3

func (a *APITest) Method(method string) *Request

Method is a builder method for setting the http method of the request

func (*APITest) Mocks

func (a *APITest) Mocks(mocks ...*Mock) *APITest

Mocks is a builder method for setting the mocks

func (*APITest) Observe

func (a *APITest) Observe(observers ...Observe) *APITest

Observe is a builder method for setting the observers

func (*APITest) ObserveMocks

func (a *APITest) ObserveMocks(observer Observe) *APITest

ObserveMocks is a builder method for setting the mocks observers

func (*APITest) Patch added in v1.0.3

func (a *APITest) Patch(url string) *Request

Patch is a convenience method for setting the request as http.MethodPatch

func (*APITest) Post added in v1.0.3

func (a *APITest) Post(url string) *Request

Post is a convenience method for setting the request as http.MethodPost

func (*APITest) Put added in v1.0.3

func (a *APITest) Put(url string) *Request

Put is a convenience method for setting the request as http.MethodPut

func (*APITest) RecorderHook

func (a *APITest) RecorderHook(hook RecorderHook) *APITest

RecorderHook allows the consumer to provider a function that will receive the recorder instance before the test runs. This can be used to inject custom events which can then be rendered in diagrams

func (*APITest) Report added in v1.0.2

func (a *APITest) Report(reporter ReportFormatter) *APITest

Reporter provides a hook to add custom formatting to the output of the test

func (*APITest) Request

func (a *APITest) Request() *Request

Request returns the request spec

func (*APITest) Response

func (a *APITest) Response() *Response

Response returns the expected response

type Assert

type Assert func(*http.Response, *http.Request) error

Assert is a user defined custom assertion function

var IsClientError Assert = func(response *http.Response, request *http.Request) error {
	if response.StatusCode >= 400 && response.StatusCode < 500 {
		return nil
	}
	return errors.New("not a client error. Status code=" + strconv.Itoa(response.StatusCode))
}

IsClientError is a convenience function to assert on a range of client error status codes

var IsServerError Assert = func(response *http.Response, request *http.Request) error {
	if response.StatusCode >= 500 {
		return nil
	}
	return errors.New("not a server error. Status code=" + strconv.Itoa(response.StatusCode))
}

IsServerError is a convenience function to assert on a range of server error status codes

var IsSuccess Assert = func(response *http.Response, request *http.Request) error {
	if response.StatusCode >= 200 && response.StatusCode < 400 {
		return nil
	}
	return errors.New("not a client error. Status code=" + strconv.Itoa(response.StatusCode))
}

IsSuccess is a convenience function to assert on a range of happy path status codes

type Cookie struct {
	// contains filtered or unexported fields
}

func NewCookie

func NewCookie(name string) *Cookie

func (*Cookie) Domain

func (cookie *Cookie) Domain(domain string) *Cookie

func (*Cookie) Expires

func (cookie *Cookie) Expires(expires time.Time) *Cookie

func (*Cookie) HttpOnly

func (cookie *Cookie) HttpOnly(httpOnly bool) *Cookie

func (*Cookie) MaxAge

func (cookie *Cookie) MaxAge(maxAge int) *Cookie

func (*Cookie) Path

func (cookie *Cookie) Path(path string) *Cookie

func (*Cookie) Secure

func (cookie *Cookie) Secure(secure bool) *Cookie

func (*Cookie) ToHttpCookie

func (cookie *Cookie) ToHttpCookie() *http.Cookie

func (*Cookie) Value

func (cookie *Cookie) Value(value string) *Cookie

type Event

type Event interface {
	GetTime() time.Time
}

type FinalResponse

type FinalResponse struct {
	// contains filtered or unexported fields
}

type HTMLTemplateModel

type HTMLTemplateModel struct {
	Title          string
	SubTitle       string
	StatusCode     int
	BadgeClass     string
	LogEntries     []LogEntry
	WebSequenceDSL string
	MetaJSON       template.JS
}

func NewHTMLTemplateModel

func NewHTMLTemplateModel(r *Recorder) (HTMLTemplateModel, error)

type HttpRequest

type HttpRequest struct {
	Source    string
	Target    string
	Value     *http.Request
	Timestamp time.Time
}

func (HttpRequest) GetTime

func (r HttpRequest) GetTime() time.Time

type HttpResponse

type HttpResponse struct {
	Source    string
	Target    string
	Value     *http.Response
	Timestamp time.Time
}

func (HttpResponse) GetTime

func (r HttpResponse) GetTime() time.Time

type InboundRequest

type InboundRequest struct {
	// contains filtered or unexported fields
}

type Intercept

type Intercept func(*http.Request)

Intercept will be called before the request is made. Updates to the request will be reflected in the test

type LogEntry

type LogEntry struct {
	Header    string
	Body      string
	Timestamp time.Time
}

func NewHttpRequestLogEntry added in v1.0.6

func NewHttpRequestLogEntry(req *http.Request) (LogEntry, error)

func NewHttpResponseLogEntry added in v1.0.6

func NewHttpResponseLogEntry(res *http.Response) (LogEntry, error)

type Matcher

type Matcher func(*http.Request, *MockRequest) error

Matcher type accepts the actual request and a mock request to match against. Will return an error that describes why there was a mismatch if the inputs do not match or nil if they do.

type MessageRequest

type MessageRequest struct {
	Source    string
	Target    string
	Header    string
	Body      string
	Timestamp time.Time
}

func (MessageRequest) GetTime

func (r MessageRequest) GetTime() time.Time

type MessageResponse

type MessageResponse struct {
	Source    string
	Target    string
	Header    string
	Body      string
	Timestamp time.Time
}

func (MessageResponse) GetTime

func (r MessageResponse) GetTime() time.Time

type Mock

type Mock struct {
	// contains filtered or unexported fields
}

func NewMock

func NewMock() *Mock

func (*Mock) Delete

func (m *Mock) Delete(u string) *MockRequest

func (*Mock) Get

func (m *Mock) Get(u string) *MockRequest

func (*Mock) Method

func (m *Mock) Method(method string) *MockRequest

func (*Mock) Patch

func (m *Mock) Patch(u string) *MockRequest

func (*Mock) Post

func (m *Mock) Post(u string) *MockRequest

func (*Mock) Put

func (m *Mock) Put(u string) *MockRequest

type MockRequest

type MockRequest struct {
	// contains filtered or unexported fields
}

func (*MockRequest) Body

func (r *MockRequest) Body(b string) *MockRequest

func (*MockRequest) Header

func (r *MockRequest) Header(key, value string) *MockRequest

func (*MockRequest) Headers

func (r *MockRequest) Headers(headers map[string]string) *MockRequest

func (*MockRequest) Query

func (r *MockRequest) Query(key, value string) *MockRequest

func (*MockRequest) QueryParams

func (r *MockRequest) QueryParams(queryParams map[string]string) *MockRequest

func (*MockRequest) QueryPresent

func (r *MockRequest) QueryPresent(key string) *MockRequest

func (*MockRequest) RespondWith

func (r *MockRequest) RespondWith() *MockResponse

type MockResponse

type MockResponse struct {
	// contains filtered or unexported fields
}

func (*MockResponse) Body

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

func (*MockResponse) Cookie

func (r *MockResponse) Cookie(name, value string) *MockResponse

func (*MockResponse) Cookies

func (r *MockResponse) Cookies(cookie ...*Cookie) *MockResponse

func (*MockResponse) End

func (r *MockResponse) End() *Mock

func (*MockResponse) Header

func (r *MockResponse) Header(key string, value string) *MockResponse

func (*MockResponse) Headers

func (r *MockResponse) Headers(headers map[string]string) *MockResponse

func (*MockResponse) Status

func (r *MockResponse) Status(statusCode int) *MockResponse

func (*MockResponse) Times

func (r *MockResponse) Times(times int) *MockResponse

type Observe

type Observe func(*http.Response, *http.Request, *APITest)

Observe will be called by with the request and response on completion

type Recorder

type Recorder struct {
	Title    string
	SubTitle string
	Meta     map[string]interface{}
	Events   []Event
}

func NewTestRecorder

func NewTestRecorder() *Recorder

func (*Recorder) AddHttpRequest

func (r *Recorder) AddHttpRequest(req HttpRequest) *Recorder

func (*Recorder) AddHttpResponse

func (r *Recorder) AddHttpResponse(req HttpResponse) *Recorder

func (*Recorder) AddMessageRequest

func (r *Recorder) AddMessageRequest(m MessageRequest) *Recorder

func (*Recorder) AddMessageResponse

func (r *Recorder) AddMessageResponse(m MessageResponse) *Recorder

func (*Recorder) AddMeta

func (r *Recorder) AddMeta(meta map[string]interface{}) *Recorder

func (*Recorder) AddSubTitle

func (r *Recorder) AddSubTitle(subTitle string) *Recorder

func (*Recorder) AddTitle

func (r *Recorder) AddTitle(title string) *Recorder

func (*Recorder) ResponseStatus

func (r *Recorder) ResponseStatus() (int, error)

type RecorderHook

type RecorderHook func(*Recorder)

type ReportFormatter

type ReportFormatter interface {
	Format(*Recorder)
}

type Request

type Request struct {
	// contains filtered or unexported fields
}

Request is the user defined request that will be invoked on the handler under test

func (*Request) BasicAuth

func (r *Request) BasicAuth(auth string) *Request

BasicAuth is a builder method to sets basic auth on the request. The credentials should be provided delimited by a colon, e.g. "username:password"

func (*Request) Body

func (r *Request) Body(b string) *Request

Body is a builder method to set the request body

func (*Request) Cookie

func (r *Request) Cookie(name, value string) *Request

Cookie is a convenience method for setting a single request cookies by name and value

func (*Request) Cookies

func (r *Request) Cookies(c ...*Cookie) *Request

Cookies is a builder method to set the request cookies

func (*Request) Expect

func (r *Request) Expect(t *testing.T) *Response

Expect marks the request spec as complete and following code will define the expected response

func (*Request) Header

func (r *Request) Header(key, value string) *Request

Header is a builder method to set the request headers

func (*Request) Headers

func (r *Request) Headers(headers map[string]string) *Request

Headers is a builder method to set the request headers

func (*Request) JSON

func (r *Request) JSON(b string) *Request

JSON is a convenience method for setting the request body and content type header as "application/json"

func (*Request) Query

func (r *Request) Query(key, value string) *Request

Query is a convenience method to add a query parameter to the request.

func (*Request) QueryCollection

func (r *Request) QueryCollection(q map[string][]string) *Request

QueryCollection is a builder method to set the request query parameters This can be used in combination with request.Query

func (*Request) QueryParams

func (r *Request) QueryParams(params map[string]string) *Request

QueryParams is a builder method to set the request query parameters. This can be used in combination with request.QueryCollection

func (*Request) URL

func (r *Request) URL(url string) *Request

URL is a builder method for setting the url of the request

type Response

type Response struct {
	// contains filtered or unexported fields
}

Response is the user defined expected response from the application under test

func (*Response) Assert

func (r *Response) Assert(fn func(*http.Response, *http.Request) error) *Response

Assert allows the consumer to provide a user defined function containing their own custom assertions

func (*Response) Body

func (r *Response) Body(b string) *Response

Body is the expected response body

func (*Response) CookieNotPresent

func (r *Response) CookieNotPresent(cookieName string) *Response

CookieNotPresent is used to assert that a cookie is not present in the response

func (*Response) CookiePresent

func (r *Response) CookiePresent(cookieName string) *Response

CookiePresent is used to assert that a cookie is present in the response, regardless of its value

func (*Response) Cookies

func (r *Response) Cookies(cookies ...*Cookie) *Response

Cookies is the expected response cookies

func (*Response) End

func (r *Response) End()

End runs the test and all defined assertions

func (*Response) Header

func (r *Response) Header(key, value string) *Response

Header is a builder method to set the request headers

func (*Response) Headers

func (r *Response) Headers(headers map[string]string) *Response

Headers is a builder method to set the request headers

func (*Response) Status

func (r *Response) Status(s int) *Response

Status is the expected response http status code

type SequenceDiagramFormatter

type SequenceDiagramFormatter struct {
	// contains filtered or unexported fields
}

func SequenceDiagram added in v1.0.2

func SequenceDiagram(path ...string) *SequenceDiagramFormatter

func (*SequenceDiagramFormatter) Format

func (r *SequenceDiagramFormatter) Format(recorder *Recorder)

type Transport

type Transport struct {
	// contains filtered or unexported fields
}

func (*Transport) Hijack

func (r *Transport) Hijack()

func (*Transport) Reset

func (r *Transport) Reset()

func (*Transport) RoundTrip

func (r *Transport) RoundTrip(req *http.Request) (mockResponse *http.Response, matchErrors error)

RoundTrip implementation intended to match a given expected mock request or throw an error with a list of reasons why no match was found.

type WebSequenceDiagramDSL

type WebSequenceDiagramDSL struct {
	// contains filtered or unexported fields
}

func (*WebSequenceDiagramDSL) AddRequestRow

func (r *WebSequenceDiagramDSL) AddRequestRow(source string, target string, description string)

func (*WebSequenceDiagramDSL) AddResponseRow

func (r *WebSequenceDiagramDSL) AddResponseRow(source string, target string, description string)

func (*WebSequenceDiagramDSL) ToString

func (r *WebSequenceDiagramDSL) ToString() string

Directories

Path Synopsis
examples module

Jump to

Keyboard shortcuts

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