simular

package module
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Apr 30, 2019 License: MIT Imports: 12 Imported by: 0

README

simular Build Status GoDoc

This library is a fork of https://github.com/jarcoal/httpmock, which now has a somewhat different API from the original library. All of the clever stuff was worked out in the original version, this fork just alters the API for creating stubbed requests, adds some extra fussiness in how it registers and responds to requests, and adds an API for verifying that all stubbed requests were actually called.

Originally this library was forked here: https://github.com/thingful/httpmock, but the API here has changed sufficiently that we decided to just create a completely new project, which has been renamed as simular.

What is the purpose of this fork?

The reason for creating this fork was that while the original httpmock library provided a very neat mechanism for inserting mocked responses into the default Go net/http client, it was intentionally designed to be very tolerant about the requests it matched which lead to us seeing some bugs in production.

To give a specific example let's say our code requires requesting data from some external resource (http://api.example.com/resource/1), and for correct operation it must include in that request a specific authorization header containing a value passed in from somewhere else within the program. For this scenario the previous library would let us create a responder for the resource http://api.example.com/resource/1, but wouldn't allow us to force a test failure if that resource was requested without the authorization header being present.

Usage examples

For usage examples, please see the documentation at https://godoc.org/github.com/thingful/simular.

Alternatives

  • httpmock - the original library this code was based on.
  • gock - Gock does everything this library does and more, and if it had existed when this codebase was first created I probably would have just used that instead.
  • govcr - port of the Ruby VCR gem. Allows recording of requests and responses which can then be played back within tests.

Documentation

Overview

Package simular provides tools for mocking HTTP responses.

Simple Example:

func TestFetchArticles(t *testing.T) {
	simular.Activate()
	defer simular.DeactivateAndReset()

	simular.RegisterStubRequests(
		simular.NewStubRequest(
			"GET",
			"https://api.mybiz.com/articles.json",
			simular.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`),
		),
	)

	// do stuff that makes a request to articles.json

	// verify that all stubs were called
	if err := simular.AllStubsCalled(); err != nil {
			t.Errorf("Not all stubs were called: %s", err)
	}
}

Advanced Example:

func TestFetchArticles(t *testing.T) {
	simular.Activate(
		WithAllowedHosts("localhost"),
	)
	defer simular.DeactivateAndReset()

	// our database of articles
	articles := make([]map[string]interface{}, 0)

	// mock to list out the articles
	simular.RegisterStubRequests(
		simular.NewStubRequest(
			"GET",
			"https://api.mybiz.com/articles.json",
			func(req *http.Request) (*http.Response, error) {
				resp, err := simular.NewJsonResponse(200, articles)
				if err != nil {
					return simular.NewStringResponse(500, ""), nil
				}
				return resp
			},
			simular.WithHeader(
				&http.Header{
					"Api-Key": []string{"1234abcd"},
				},
			),
		),
	)

	// mock to add a new article
	simular.RegisterStubRequests(
		simular.NewStubRequest(
			"POST",
			"https://api.mybiz.com/articles.json",
			func(req *http.Request) (*http.Response, error) {
				article := make(map[string]interface{})
				if err := json.NewDecoder(req.Body).Decode(&article); err != nil {
					return simular.NewStringResponse(400, ""), nil
				}

				articles = append(articles, article)

				resp, err := simular.NewJsonResponse(200, article)
				if err != nil {
					return simular.NewStringResponse(500, ""), nil
				}
				return resp, nil
			},
			simular.WithHeader(
				&http.Header{
					"Api-Key": []string{"1234abcd"},
				},
			),
			simular.WithBody(
				bytes.NewBufferString(`{"title":"article"}`),
			),
		),
	)

	// do stuff that adds and checks articles

	// verify that all stubs were called
	if err := simular.AllStubsCalled(); err != nil {
			t.Errorf("Not all stubs were called: %s", err)
	}
}

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNoResponders = NewErrNoResponderFound(nil)

ErrNoResponders is an instance of ErrNoResponderFound created when no errors returned, i.e. no registered responders

Functions

func Activate

func Activate(opts ...func())

Activate starts the mock environment. This should be called before your tests run. Under the hood this replaces the Transport on the http.DefaultClient with mockTransport.

To enable mocks for a test, simply activate at the beginning of a test:

func TestFetchArticles(t *testing.T) {
	simular.Activate()
	// all http requests will now be intercepted
}

If you want all of your tests in a package to be mocked, just call Activate from init():

func init() {
	simular.Activate()
}

Activate takes a variadic list of functions to configure the behaviour of simular.

func ActivateNonDefault

func ActivateNonDefault(client *http.Client, opts ...func())

ActivateNonDefault starts the mock environment with a non-default http.Client. This emulates the Activate function, but allows for custom clients that do not use http.DefaultTransport

To enable mocks for a test using a custom client, activate at the beginning of a test:

client := &http.Client{Transport: &http.Transport{TLSHandshakeTimeout: 60 * time.Second}}
simular.ActivateNonDefault(client)

func AllStubsCalled

func AllStubsCalled() error

AllStubsCalled is a function intended to be used within your tests to verify that all registered stubs were actually invoked during the course of the test. Registering stubs but then not calling them is either a sign that something is wrong in your test, or a waste of time. This function retuns an error unless all currently registered stubs were called.

func ConnectionFailure

func ConnectionFailure(req *http.Request, err error) (*http.Response, error)

ConnectionFailure is a responder that returns a connection failure. This is the default responder, and is called when no other matching responder is found.

func Deactivate

func Deactivate()

Deactivate shuts down the mock environment. Any HTTP calls made after this will use a live transport.

Usually you'll call it in a defer right after activating the mock environment:

func TestFetchArticles(t *testing.T) {
	simular.Activate()
	defer simular.Deactivate()

	// when this test ends, the mock environment will close
}

func DeactivateAndReset

func DeactivateAndReset()

DeactivateAndReset is just a convenience method for calling Deactivate() and then Reset() Happy deferring!

func Disabled

func Disabled() bool

Disabled returns true if the GONOMOCKS environment variable is not empty

func NewBytesResponse

func NewBytesResponse(status int, body []byte) *http.Response

NewBytesResponse creates an *http.Response with a body based on the given bytes. Also accepts an http status code.

func NewJSONResponse

func NewJSONResponse(status int, body interface{}) (*http.Response, error)

NewJSONResponse creates an *http.Response with a body that is a json encoded representation of the given interface{}. Also accepts an http status code.

func NewRespBodyFromBytes

func NewRespBodyFromBytes(body []byte) io.ReadCloser

NewRespBodyFromBytes creates an io.ReadCloser from a byte slice that is suitable for use as an http response body.

func NewRespBodyFromString

func NewRespBodyFromString(body string) io.ReadCloser

NewRespBodyFromString creates an io.ReadCloser from a string that is suitable for use as an http response body.

func NewStringResponse

func NewStringResponse(status int, body string) *http.Response

NewStringResponse creates an *http.Response with a body based on the given string. Also accepts an http status code.

func NewXMLResponse

func NewXMLResponse(status int, body interface{}) (*http.Response, error)

NewXMLResponse creates an *http.Response with a body that is an xml encoded representation of the given interface{}. Also accepts an http status code.

func RegisterNoResponder

func RegisterNoResponder(responder Responder)

RegisterNoResponder adds a mock that will be called whenever a request for an unregistered URL is received. The default behavior is to return a connection error.

In some cases you may not want all URLs to be mocked, in which case you can do this:

func TestFetchArticles(t *testing.T) {
	simular.Activate()
	defer simular.DeactivateAndReset()
	simular.RegisterNoResponder(simular.InitialTransport.RoundTrip)

	// any requests that don't have a registered URL will be fetched normally
}

func RegisterStubRequests

func RegisterStubRequests(requests ...*StubRequest)

RegisterStubRequests adds multiple stubbed requests that will catch requests to the given HTTP methods and URLs, then route them to the appropriate Responder which will generate a response to be returned to the client.

Example
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/thingful/simular"
)

func main() {
	simular.Activate()
	defer simular.DeactivateAndReset()

	simular.RegisterStubRequests(
		simular.NewStubRequest(
			"GET",
			"http://example.com/",
			simular.NewStringResponder(200, "ok"),
		),
		simular.NewStubRequest(
			"GET",
			"http://another.com/",
			simular.NewStringResponder(200, "also ok"),
		),
	)

	resp, err := http.Get("http://example.com/")
	if err != nil {
		// handle error properly in real code
		panic(err)
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// handle error properly in real code
		panic(err)
	}

	fmt.Println(string(body))

	resp, err = http.Get("http://another.com/")
	if err != nil {
		// handle error properly in real code
		panic(err)
	}

	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		// handle error properly in real code
		panic(err)
	}

	fmt.Println(string(body))

	if err = simular.AllStubsCalled(); err != nil {
		// handle error properly in real code
		panic(err)
	}

}
Output:

ok
also ok

func Reset

func Reset()

Reset will remove any registered mocks and return the mock environment to it's initial state.

func WithAllowedHosts

func WithAllowedHosts(hosts ...string) func()

WithAllowedHosts is used to configure the behaviour of simular, by allowing clients to specify a list of allowed hosts when calling Activate. A list of hostnames excluding any scheme or port parts can be passed here, and any requests matching that hostname will be allowed to proceed as normal.

Types

type ErrNoResponderFound

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

ErrNoResponderFound is a custom error type used when no responders were found.

func NewErrNoResponderFound

func NewErrNoResponderFound(errs []error) *ErrNoResponderFound

NewErrNoResponderFound returns a new ErrNoResponderFound error

func (*ErrNoResponderFound) Error

func (e *ErrNoResponderFound) Error() string

Error ensures our ErrNoResponderFound type implements the error interface

type ErrStubsNotCalled

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

ErrStubsNotCalled is a type implementing the error interface we return when not all registered stubs were called

func NewErrStubsNotCalled

func NewErrStubsNotCalled(uncalledStubs []*StubRequest) *ErrStubsNotCalled

NewErrStubsNotCalled returns a new StubsNotCalled error

func (*ErrStubsNotCalled) Error

func (e *ErrStubsNotCalled) Error() string

Error ensures our ErrStubsNotCalled type implements the error interface

type MockTransport

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

MockTransport implements http.RoundTripper, which fulfills single http requests issued by an http.Client. This implementation doesn't actually make the call, instead deferring to the registered list of stubbed requests.

func NewMockTransport

func NewMockTransport() *MockTransport

NewMockTransport creates a new *MockTransport with no stubbed requests.

func (*MockTransport) AllStubsCalled

func (m *MockTransport) AllStubsCalled() error

AllStubsCalled returns nil if all of the currently registered stubs have been called; if some haven't been called, then it returns an error.

func (*MockTransport) CancelRequest

func (m *MockTransport) CancelRequest(req *http.Request)

CancelRequest does nothing with timeout

func (*MockTransport) RegisterNoResponder

func (m *MockTransport) RegisterNoResponder(responder Responder)

RegisterNoResponder is used to register a responder that will be called if no other responder is found. The default is ConnectionFailure.

func (*MockTransport) RegisterStubRequests

func (m *MockTransport) RegisterStubRequests(stubs ...*StubRequest)

RegisterStubRequests adds multiple stub requests with associated responders. When a request comes in that matches, the appropriate responder will be called and the response returned to the client.

func (*MockTransport) Reset

func (m *MockTransport) Reset()

Reset removes all registered responders (including the no responder) from the MockTransport

func (*MockTransport) RoundTrip

func (m *MockTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip receives HTTP requests and routes them to the appropriate responder. It is required to implement the http.RoundTripper interface. You will not interact with this directly, instead the *http.Client you are using will call it for you.

type Option

type Option func(*StubRequest)

Option is a functional configurator allowing non-standard configuration to be added to the StubRequest without the previous chained function call approach.

func WithBody

func WithBody(body io.Reader) Option

WithBody is a functional configuration option used to add a body to a stubbed request

func WithHeader

func WithHeader(header *http.Header) Option

WithHeader is a functional configuration option used to add http headers onto a stubbed request

type Responder

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

Responder types are callbacks that receive and http request and return a mocked response.

func NewBytesResponder

func NewBytesResponder(status int, body []byte) Responder

NewBytesResponder creates a Responder from a given body (as a byte slice) and status code.

func NewJSONResponder

func NewJSONResponder(status int, body interface{}) (Responder, error)

NewJSONResponder creates a Responder from a given body (as an interface{} that is encoded to json) and status code.

func NewStringResponder

func NewStringResponder(status int, body string) Responder

NewStringResponder creates a Responder from a given body (as a string) and status code.

func NewXMLResponder

func NewXMLResponder(status int, body interface{}) (Responder, error)

NewXMLResponder creates a Responder from a given body (as an interface{} that is encoded to xml) and status code.

func ResponderFromResponse

func ResponderFromResponse(resp *http.Response) Responder

ResponderFromResponse wraps an *http.Response in a Responder

type StubRequest

type StubRequest struct {
	Method    string
	URL       string
	Header    *http.Header
	Body      io.Reader
	Responder Responder
	Called    bool
}

StubRequest is used to capture data about a new stubbed request. It wraps up the Method and URL along with optional http.Header struct, holds the Responder used to generate a response, and also has a flag indicating whether or not this stubbed request has actually been called.

func NewStubRequest

func NewStubRequest(method, url string, responder Responder, options ...Option) *StubRequest

NewStubRequest is a constructor function that returns a StubRequest for the given method and url. We also supply a responder which actually generates the response should the stubbed request match the request.

func (*StubRequest) Matches

func (r *StubRequest) Matches(req *http.Request) error

Matches is a function that returns true if an incoming request is matched by this fetcher. Should an incoming request URL cause an error when normalized, we return false.

func (*StubRequest) String

func (r *StubRequest) String() string

String is our implementation of Stringer for our StubRequest types. Returns a simple string representation of the stubbed response, included method, URL and any headers.

Jump to

Keyboard shortcuts

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