fipple

package module
v0.0.0-...-25967ca Latest Latest
Warning

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

Go to latest
Published: Jul 27, 2015 License: MIT Imports: 14 Imported by: 0

README

Fipple

GoDoc

A lightweight utility written in go that makes it easy to write integration tests for REST APIs.

Installation

Install like you would any other go package

go get github.com/albrow/fipple

Then import the package in your source code

import "github.com/albrow/fipple"

Setting Up

Fipple is designed for integration testing. It helps you test the endpoints of REST APIs by sending requests to your server, recording the results, and providing convenient methods for checking the response.

Start with the Recorder object, which you can use to create and send http requests and record the response. The Recorder will use a *testing.T to report errors in a very readable format.

The second argument to fipple.NewRecorder is an http.Handler. The recorder will route all requests through the the given handler. Here's an example of how to use fipple with httptest and the martini web framework.

func TestUsersCreate(t *testing.T) {
	// Create a martini instance and add a single route handler. Typically you
	// would want to wrap this into a function that declares all your routes.
	m := martini.Classic()
	m.Get("/", func() string {
		return "Hello world!"
	})
	// Create a new recorder using the martini instance. Requests sent using the
	// recorder will be sent through the martini instance.
	rec := fipple.NewRecorder(t, m)
}

We used martini in the above example, but other popular web frameworks such as gin or negroni would work just as well. You can create a recorder with anything that implements http.Handler.

If you're using a web framework that does not expose an http.Handler, don't fret! You can still use fipple by starting a server in a separate process and then using fipple.NewURLRecorder to create the recorder. So, for example if you started your server in a separate process listening on port 4000, here's how you would create a recorder:

func TestUsersCreate(t *testing.T) {
	// Create a recorder that points to our already running server on port 4000.
	rec := fipple.NewURLRecorder(t, "http://localhost:4000")
}

Example Usage

In this example, we're writing an integration test for creating users. We'll use the Recorder object to construct and send a POST request with some parameters for creating a user. The response will be captured in a Response object, a lightweight wrapper around an http.Response, which we'll call res. We'll use the Post method for this. It takes two arguments: the path (which is appended to the base url for the Recorder) and the data you want to send as a map of string to string. Here, we'll do a POST request to "/users" with an email and password.

res := rec.Post("/users", map[string]string{
	"email": "foo@example.com",
	"name": "Mr. Foo Bar",
	"password": "password123",
})

Note that there's no need to write error handling yourself. Since rec has a *testing.T, it'll report errors automatically with t.Error.

We can then use the Response object to make sure the response from our server was correct. Typically this means using the ExpectOk (or ExpectCode if you expect a code other than 200) and ExpectBodyContains methods. ExpectBodyContains will check the body of the response for the provided string. If the body does not contain that string, it reports an error via t.Error.

In our example, we expect the response code to be 200 (i.e. Ok) and the body of the response to contain the user we just created, encoded as JSON. Here's how we could do that:

res.ExpectOk()
res.ExpectBodyContains(`"user": `)
res.ExpectBodyContains(`"email": "foo@example.com"`)
res.ExpectBodyContains(`"name": "Mr. Foo Bar"`)

If any of the expectations failed, fipple will print out a nice summary of the request, including the actual body of the response, and a list of what went wrong. Here's an example output for a failed test:

Example Failed Test Output

If the response contains JSON, it will automatically be formatted for you. You can also turn the colorization off via the Colorize option. (With colorization off, the body of the response will be the same color as all other text, instead of dark grey-ish).

Table-Driven Tests

Fipple works great for table-driven tests. Let's say that you wanted to test validations for the create users endpoint. For inputs that are incorrect, you expect a specific validation error in the response indicating what was wrong. Here's an example:

validationTests := []struct {
	data            map[string]string
	expectedMessage string
}{
	{
		data: map[string]string{
			"email": "foo2@example.com",
		},
		expectedMessage: "name is required",
	},
	{
		data: map[string]string{
			"email": "not_valid_email",
		},
		expectedMessage: "email is invalid",
	},
	{
		data: map[string]string{
			"name": "Foo Bar Jr.",
		},
		expectedMessage: "email is required",
	},
}
for _, test := range validationTests {
	res := rec.Post("users", test.data)
	res.ExpectCode(422)
	res.ExpectBodyContains(test.expectedMessage)
}

More Complicated Requests

Right out of the box, the Recorder object supports the GET, POST, PUT, and DELETE methods. If you need to test a different type of method, you can do so by constructing your own request and then using the Do method of the Recorder. Here's an example:

req := rec.NewRequest("BREW", "coffees/french_roast")
res := rec.Do(req)
res.ExpectCode(418)
res.ExpectBodyContains("I am a teapot!")

Since NewRequest returns a vanilla *http.Request object, you can also use it to add custom headers to the request before passing the request to Do. Here's an example of adding a JWT token to the request before sending it.

req := rec.NewRequest("DELETE", "users/" + userId)
req.Header.Add("AUTHORIZATION", "Bearer " + authToken)
res := rec.Do(req)
res.ExpectOk()

Finally, fipple also supports multipart requests (with file uploads!) via the NewMultipartRequest method.

userFields := map[string]string{
	"email":    "bar@example.com",
	"name":     "Ms. Foo Bar",
	"password": "password123",
}
profileImageFile, err := os.Open("images/profile.png")
if err != nil {
	t.Fatal(err)
}
userFiles := map[string]*os.File{
	"profilePicture": profileImageFile,
}
req := rec.NewMultipartRequest("POST", "users", userFields, userFiles)
res := rec.Do(req)
res.ExpectOk()

Full documentation is available on godoc.org.

License

Fipple is licensed under the MIT License. See the LICENSE file for more information.

Documentation

Overview

Package fipple is a lightweight utility that makes it easy to write integration tests for REST APIs.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Recorder

type Recorder struct {

	// Colorize is used to determine whether or not to colorize the errors when
	// printing to the console using t.Error. The default is true.
	Colorize bool
	// contains filtered or unexported fields
}

Recorder can be used to send http requests and record the responses.

func NewRecorder

func NewRecorder(t *testing.T, handler http.Handler) *Recorder

NewRecorder returns a recorder that sends requests through the given handler. The recorder will report any errors using t.Error or t.Fatal.

func NewURLRecorder

func NewURLRecorder(t *testing.T, baseURL string) *Recorder

NewURLRecorder creates a new recorder with the given baseURL. The recorder will report any errors using t.Error or t.Fatal.

func (*Recorder) Close

func (r *Recorder) Close()

Close closes the recorder. You must call Close when you are done using a recorder.

func (*Recorder) Delete

func (r *Recorder) Delete(path string) *Response

Delete sends a DELETE request to the given path and records the results into a fipple.Response. path will be appended to the baseURL for the recorder to create the full URL. You can run methods on the response to check the results. Any errors that occur will be passed to t.Fatal

func (*Recorder) Do

func (r *Recorder) Do(req *http.Request) *Response

Do sends req and records the results into a fipple.Response. Note that because an http.Request should have already been created with a full, valid url, the baseURL of the Recorder will not be prepended to the url for req. You can run methods on the response to check the results. Any errors that occur will be passed to t.Fatal

func (*Recorder) Get

func (r *Recorder) Get(path string) *Response

Get sends a GET request to the given path and records the results into a fipple.Response. path will be appended to the baseURL for the recorder to create the full URL. You can run methods on the response to check the results. Any errors that occur will be passed to t.Fatal

func (*Recorder) GetCookies

func (r *Recorder) GetCookies() []*http.Cookie

GetCookies returns the raw cookies that have been set as a result of any requests recorded by a Recorder. Any errors that occur will be passed to t.Fatal

func (*Recorder) NewJSONRequest

func (r *Recorder) NewJSONRequest(method string, path string, data interface{}) *http.Request

NewJSONRequest creates and returns a JSON request with the given method and path (which is appended to the baseURL. data can be any data structure but cannot include functions or recursiveness. NewJSONRequest will convert data into json using json.Marshall. The Content-Type header will automatically be added. Any errors tha occur will be passed to t.Fatal.

func (*Recorder) NewMultipartRequest

func (r *Recorder) NewMultipartRequest(method string, path string, fields map[string]string, files map[string]*os.File) *http.Request

NewMultipartRequest can be used to easily create (and later send) a request with form data and/or files (encoded as multipart/form-data). fields is a key-value map of basic string fields for the form data, and files is a map of key to *os.File. The Content-Type header will automatically be added. Any errors tha occur will be passed to t.Fatal.

func (*Recorder) NewRequest

func (r *Recorder) NewRequest(method string, path string) *http.Request

NewRequest creates a new request object with the given http method and path. The path will be appended to the baseURL for the recorder to create the full URL. You are free to add additional parameters or headers to the request before sending it. Any errors that occur will be passed to t.Fatal.

func (*Recorder) NewRequestWithData

func (r *Recorder) NewRequestWithData(method string, path string, data map[string]string) *http.Request

NewRequestWithData can be used to easily send a request with form data (encoded as application/x-www-form-urlencoded). The path will be appended to the baseURL for the recorder to create the full URL. The Content-Type header will automatically be added. Any errors tha occur will be passed to t.Fatal.

func (*Recorder) Post

func (r *Recorder) Post(path string, data map[string]string) *Response

Post sends a POST request to the given path using the given data as post parameters and records the results into a fipple.Response. path will be appended to the baseURL for the recorder to create the full URL. You can run methods on the response to check the results. Any errors that occur will be passed to t.Fatal

func (*Recorder) Put

func (r *Recorder) Put(path string, data map[string]string) *Response

Put sends a PUT request to the given path using the given data as parameters and records the results into a fipple.Response. path will be appended to the baseURL for the recorder to create the full URL. You can run methods on the response to check the results. Any errors that occur will be passed to t.Fatal

type Response

type Response struct {
	*http.Response
	Body []byte
	// contains filtered or unexported fields
}

Response represents the response from an http request and has methods to make testing easier.

func (*Response) ExpectBodyContains

func (r *Response) ExpectBodyContains(str string)

ExpectBodyContains causes a test error if the response body does not contain the given string.

func (*Response) ExpectCode

func (r *Response) ExpectCode(code int)

ExpectCode causes a test error if response code != the given code

func (*Response) ExpectOk

func (r *Response) ExpectOk()

ExpectOk causes a test error if response code != 200

func (*Response) PrintFailure

func (r *Response) PrintFailure()

PrintFailure prints some information about the response via t.Errorf. This includes the method, the url, and the response body. If the Content-Type of the response is application/json, PrintFailure will automatically indent it.

func (*Response) PrintFailureOnce

func (r *Response) PrintFailureOnce()

PrintFailureOnce is like PrintFailure but only prints out the information once per response, regardless of how many times it is called.

func (*Response) Unmarshal

func (r *Response) Unmarshal(v interface{}) error

Unmarshal unmarshals the response body into v.

Jump to

Keyboard shortcuts

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