handlertest

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Mar 30, 2020 License: MIT Imports: 10 Imported by: 0

README

Build Status Coverage Report Go Report Card

GoDoc License MIT

Handlertest

Toolkit for testing http handlers in a verbose way.

Two basic examples:

func TestListFilter(t *testing.T) {
	// do some inserts to test DB

	type Product struct {
		Category string
	}

	// create your request
	handlertest.Call(YourHandler).GET("/products?category=a").
		// then assert your expectations
		Assert(t).
		Status(http.StatusOK).
		JSONMatches(func(t *testing.T, products []Product) {
			// unmarshalling of JSON objects is done for you
			if len(products) == 0 {
				t.Errorf("Expected to have some products returned")
			}
			for _, p := range products {
				if p.Category != "a" {
					t.Errorf("Expected filter to return products only of category %s, but got %s", 
						"a", p.Category)
				}
			}
		})
}

// or

func TestUploadAttachments(t *testing.T) {
	// create request
	handlertest.Call(blog.uploadAttachments).
		POST("/attachments").
		FormMultipartMap(map[string]string{
			"post_id": "1",
		}).
		File("files[]", "img1.jpg", "contents").
		// then assert your expectations
		Assert(t).
		Status(http.StatusCreated).
		ContentType("text/html")
}

Semver

This library follows semantic versioning, semver in go.

You might want to check the list of available versions and CHANGELOG.

Quick reference

Request

Test request is created with handlertest.Call(YourHttpHandler).

Then you can set how the request should look like by chaining methods.

handlertest.Call(YourHttpHandler).POST("/jobs").JSON(`{"name": "test"}`)

Methods below are part of Request.

Methods
  • Method("PUT") - Call handler with a given method.
  • POST() - shorthand for a POST method
  • GET() - shorthand for a GET method
Url
  • URL("/jobs?status=SUCCESSFUL") - set request's URL
Body
  • JSON(`{"name": "test"}`) - sets given json as body and adds Content-Type: application/json header.
Forms

A set of methods that encodes form values in a body. Methods creating forms of Content-Type: application/x-www-form-urlencoded

  • FormURLEncoded(values url.Values) - general method to set form fields
  • FormURLEncodedMap(values map[string]string) - a shorthand accepting single strings as field values

Methods creating forms of Content-Type: multipart/form-data:

  • FormMultipart(fields url.Values) - general method to set form fields that are not files
  • FormMultipartMap(values map[string]string) - shorthand for the above
  • File(field string, fileName string, content string) - adds a file to a given field
  • FileReader(field string, fileName string, content io.Reader) - adds a file to a given field taking content from a reader
  • Files(fields map[string]map[string]string) sets all files at once
  • FileReaders(fields map[string]map[string]io.Reader) - sets all files at once
Headers
  • Header(key string, value string) - sets a header
  • ContentType(contentType string) - shorthand for setting Content-Type
Context

TBDone

Custom modifications

Call Request to set a custom Request with modifications not covered by this lib use the below function. Please also file an issue to support your case if it is general enough.

  • Request(func(request *http.Request) *http.Request)
Integration with Gorilla Mux

Tested code at https://gitlab.com/gotests/handlertest-gorilla-mux:

func Example_gorilla_mux_test_routes() {
	PrintMuxVar := func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		fmt.Printf("%v", vars)
	}

	t := new(testing.T)

	router := mux.NewRouter()
	router.HandleFunc("/products/{key}", PrintMuxVar)

	handlertest.Call(router.ServeHTTP).GET("/products/library").
		Assert(t)

	// Output: map[key:library]
}

func Example_gorilla_mux_inject_vars() {
	PrintMuxVar := func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		fmt.Printf("%v", vars)
	}

	t := new(testing.T)

	handlertest.Call(PrintMuxVar).Request(func(r *http.Request) *http.Request {
		return mux.SetURLVars(r, map[string]string{
			"key": "value",
		})
	}).Assert(t)

	// Output: map[key:value]
}

Assert

Once you created a needed request call on it .Assert(t) to get get an object where you can specify assertions.

func TestPostForm(t *testing T) {
  handlertest.Call(yourHandler).FormURLEncodedMap(map[string]string{
    "field": "value"
  }).Assert(t).
    Status(http.StatusCreated).   .
    ContentType("text/html")
} 
Status
  • Status(statusCode int) - assert that response has specific HTTP Status Code
Headers
  • Header(key string, value string) - assert that specific header is set
  • HeaderMissing(key string) - assert that specific header is not set
  • ContentType(contentType string) - assert that response is of specific Content-Type
Body

There is one general function that lets you assert that handler provided a specific body:

  • Body(func(t *testing.T, body []byte))

To support asserting for json response common in API development there are following assertions that tests that Content-Type is set right and offer different ways to assert for the body contents:

  • JSON(`[{"id": 1}]`) - providing it as a string. Indentation here doesn't play a role and there will be an option to show diff between expected and actual values.
  • JSONUnmarshallsTo([]Obj{}) - there is simple assertion that tests unmarshalling
  • JSONMatches(func(t *testing.T, ret []Obj) - in case it's hard to predict your whole response, or you don't want to test the whole response, you might use this function to get an unmarshalled response body and test for specific values.
Context
Custom modifications

Use it to implement custom assertions you might need that are not covered by this lib use below function. Please also file an issue to support your case if it is general enough

a.Response(func(t *testing.T, response *http.Response) {
    t.Error("I don't like this response")
})

Contributing

Your issues and PRs won't be disregarded. Please do keep them coming.

There is hopefully a low-level entry barrier to become maintainer and owner of the project. There are three levels to go through:

  • Reporters - Anyone who report a bug or suggest an improvement is welcomed
  • Reviewes - Shaping development by reviewing PRs
  • Owners - Deciding on lib development. Accepting PRs

Kudos go to:

  • @rafal-brize for hardware support
  • @joncfoo for improvement suggestions
  • members of the Warsaw Golang Meetup for warm welcome and initial reviews

Documentation

Overview

Package handlertest is a toolkit for testing http handlers in a verbose way.

See assert subpackage handling assertions on http.Response https://godoc.org/gitlab.com/gotests/handlertest/assert

Example (TestListFilter)
package main

import (
	"fmt"
	"net/http"
	"testing"

	"gitlab.com/gotests/handlertest"
)

func main() {
	ProductListControllerThatLikesCategoryB := func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte(`[{"category":"b"}]`))
	}

	type Product struct {
		Category string
	}

	t := new(testing.T)

	// create your request
	handlertest.Call(ProductListControllerThatLikesCategoryB).GET("/products?category=a").
		// then assert your expectations
		Assert(t).
		Status(http.StatusOK).
		JSONMatches(func(t *testing.T, products []Product) {
			// unmarshalling of JSON objects is done for you
			if len(products) == 0 {
				t.Errorf("Expected to have some products returned")
			}
			for _, p := range products {
				if p.Category != "a" {
					t.Errorf("Expected filter to return products only of category %s, but got %s",
						"a", p.Category)

					fmt.Printf("Expected filter to return products only of category %s, but got %s",
						"a", p.Category)
				}
			}
		})

}
Output:

Expected filter to return products only of category a, but got b
Example (TestUploadAttachments)
package main

import (
	"fmt"
	"net/http"
	"testing"

	"gitlab.com/gotests/handlertest"
)

func main() {
	UploadAttachmentsController := func(w http.ResponseWriter, r *http.Request) {
		_ = r.ParseMultipartForm(1 << 10)

		fmt.Printf("post_id: %s\n", r.PostForm.Get("post_id"))
		fmt.Println(r.MultipartForm.File["files[]"][0].Filename)
	}

	t := new(testing.T)

	// create request
	handlertest.Call(UploadAttachmentsController).
		POST("/attachments").
		FormMultipartMap(map[string]string{
			"post_id": "1",
		}).
		File("files[]", "img1.jpg", "contents").
		// then assert your expectations
		Assert(t).
		Status(http.StatusCreated).
		ContentType("text/html")

}
Output:

post_id: 1
img1.jpg

Index

Examples

Constants

View Source
const ContentTypeFormURLEncoded = "application/x-www-form-urlencoded"

ContentTypeFormURLEncoded application/x-www-form-urlencoded

View Source
const ContentTypeJSON = "application/json"

ContentTypeJSON application/json

View Source
const ContentTypeMultipartFormDataPrefix = "multipart/form-data;"

ContentTypeMultipartFormDataPrefix multipart/form-data;

Variables

This section is empty.

Functions

func ValuesFromMap

func ValuesFromMap(values map[string]string) url.Values

ValuesFromMap converts map string to url.Values

Types

type Request

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

Request follows builder pattern to set how the request should look like.

func Call

func Call(handler http.HandlerFunc) *Request

Call your handler. This creates Request builder on which you can set how the request should look like by chaining methods.

func (*Request) Assert

func (r *Request) Assert(t *testing.T) *assert.Assert

Assert Return object to specify how the http.Response should look like by chaining assertions.

func (*Request) ContentType

func (r *Request) ContentType(contentType string) *Request

ContentType Shorthand for setting `Content-Type`

func (*Request) Custom

func (r *Request) Custom(customize func(request *http.Request)) *Request

Custom deprecated in favor of Response

func (*Request) File

func (r *Request) File(field string, fileName string, content string) *Request

File Sets a file on a given field with text contents Content-Type will be `multipart/form-data`

func (*Request) FileReader

func (r *Request) FileReader(field string, fileName string, content io.Reader) *Request

FileReader Sets a file on a given field with contents from io.Reader Content-Type will be `multipart/form-data`

Example
package main

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"testing"

	"gitlab.com/gotests/handlertest"
)

func main() {
	YourHandler := func(w http.ResponseWriter, r *http.Request) {
		_ = r.ParseMultipartForm(1 << 10)

		fmt.Println(r.MultipartForm.File["files[]"][0].Filename)
	}

	t := new(testing.T)

	r, _ := os.Open(filepath.Join("testdata", "1.txt"))
	handlertest.Call(YourHandler).FileReader("files[]", "1.txt", r).Assert(t)

}
Output:

1.txt

func (*Request) FileReaders

func (r *Request) FileReaders(fields map[string]map[string]io.Reader) *Request

FileReaders Sets several files with contents from io.Reader Content-Type will be `multipart/form-data`

func (*Request) Files

func (r *Request) Files(fields map[string]map[string]string) *Request

Files Sets several files with text contents Content-Type will be `multipart/form-data`

func (*Request) FormMultipart

func (r *Request) FormMultipart(fields url.Values) *Request

FormMultipart Encodes form values request's body Content-Type will be `multipart/form-data`

func (*Request) FormMultipartMap

func (r *Request) FormMultipartMap(values map[string]string) *Request

FormMultipartMap Encodes form values request's body Content-Type will be `multipart/form-data`

func (*Request) FormURLEncoded

func (r *Request) FormURLEncoded(values url.Values) *Request

FormURLEncoded Encodes form values in request's body. Content-Type will be `application/x-www-form-urlencoded`

func (*Request) FormURLEncodedMap

func (r *Request) FormURLEncodedMap(values map[string]string) *Request

FormURLEncodedMap Encodes form values in request's body. Using simple map in case your fields don't have multiple values. Content-Type will be `application/x-www-form-urlencoded`

func (*Request) GET

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

GET Shorthand for Method("GET")

func (*Request) Header

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

Header Sets a header

func (*Request) JSON

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

JSON Sets given json as body and adds `Content-Type: application/json` header

func (*Request) Method

func (r *Request) Method(method string) *Request

Method Call your handler with a given method

func (*Request) POST

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

POST Shorthand for Method("POST")

func (*Request) Request added in v1.1.0

func (r *Request) Request(customize func(request *http.Request) *http.Request) *Request

Request Use it implement custom modifications of request not covered by this lib use the below function. Please also file an issue to support your case if it is general enough.

func (*Request) URL

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

URL Call handler with given URL

Directories

Path Synopsis
Package assert implements assertions to be run on HTTP response
Package assert implements assertions to be run on HTTP response

Jump to

Keyboard shortcuts

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