463 lines
14 KiB
Go
463 lines
14 KiB
Go
// Package httpexpect helps with end-to-end HTTP and REST API testing.
|
|
//
|
|
// Usage examples
|
|
//
|
|
// See example directory:
|
|
// - https://godoc.org/github.com/gavv/httpexpect/_examples
|
|
// - https://github.com/gavv/httpexpect/tree/master/_examples
|
|
//
|
|
// Communication mode
|
|
//
|
|
// There are two common ways to test API with httpexpect:
|
|
// - start HTTP server and instruct httpexpect to use HTTP client for communication
|
|
// - don't start server and instruct httpexpect to invoke http handler directly
|
|
//
|
|
// The second approach works only if the server is a Go module and its handler can
|
|
// be imported in tests.
|
|
//
|
|
// Concrete behaviour is determined by Client implementation passed to Config struct.
|
|
// If you're using http.Client, set its Transport field (http.RoundTriper) to one of
|
|
// the following:
|
|
// 1. default (nil) - use HTTP transport from net/http (you should start server)
|
|
// 2. httpexpect.Binder - invoke given http.Handler directly
|
|
// 3. httpexpect.FastBinder - invoke given fasthttp.RequestHandler directly
|
|
//
|
|
// Note that http handler can be usually obtained from http framework you're using.
|
|
// E.g., echo framework provides either http.Handler or fasthttp.RequestHandler.
|
|
//
|
|
// You can also provide your own implementation of RequestFactory (creates http.Request),
|
|
// or Client (gets http.Request and returns http.Response).
|
|
//
|
|
// If you're starting server from tests, it's very handy to use net/http/httptest.
|
|
//
|
|
// Value equality
|
|
//
|
|
// Whenever values are checked for equality in httpexpect, they are converted
|
|
// to "canonical form":
|
|
// - structs are converted to map[string]interface{}
|
|
// - type aliases are removed
|
|
// - numeric types are converted to float64
|
|
// - non-nil interfaces pointing to nil slices and maps are replaced with
|
|
// nil interfaces
|
|
//
|
|
// This is equivalent to subsequently json.Marshal() and json.Unmarshal() the value
|
|
// and currently is implemented so.
|
|
//
|
|
// Failure handling
|
|
//
|
|
// When some check fails, failure is reported. If non-fatal failures are used
|
|
// (see Reporter interface), execution is continued and instance that was checked
|
|
// is marked as failed.
|
|
//
|
|
// If specific instance is marked as failed, all subsequent checks are ignored
|
|
// for this instance and for any child instances retrieved after failure.
|
|
//
|
|
// Example:
|
|
// array := NewArray(NewAssertReporter(t), []interface{}{"foo", 123})
|
|
//
|
|
// e0 := array.Element(0) // success
|
|
// e1 := array.Element(1) // success
|
|
//
|
|
// s0 := e0.String() // success
|
|
// s1 := e1.String() // failure; e1 and s1 are marked as failed, e0 and s0 are not
|
|
//
|
|
// s0.Equal("foo") // success
|
|
// s1.Equal("bar") // this check is ignored because s1 is marked as failed
|
|
package httpexpect
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/cookiejar"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"golang.org/x/net/publicsuffix"
|
|
)
|
|
|
|
// Expect is a toplevel object that contains user Config and allows
|
|
// to construct Request objects.
|
|
type Expect struct {
|
|
config Config
|
|
builders []func(*Request)
|
|
matchers []func(*Response)
|
|
}
|
|
|
|
// Config contains various settings.
|
|
type Config struct {
|
|
// BaseURL is a URL to prepended to all request. My be empty. If
|
|
// non-empty, trailing slash is allowed but not required and is
|
|
// appended automatically.
|
|
BaseURL string
|
|
|
|
// RequestFactory is used to pass in a custom *http.Request generation func.
|
|
// May be nil.
|
|
//
|
|
// You can use DefaultRequestFactory, or provide custom implementation.
|
|
// Useful for Google App Engine testing for example.
|
|
RequestFactory RequestFactory
|
|
|
|
// Client is used to send http.Request and receive http.Response.
|
|
// Should not be nil.
|
|
//
|
|
// You can use http.DefaultClient or http.Client, or provide
|
|
// custom implementation.
|
|
Client Client
|
|
|
|
// WebsocketDialer is used to establish websocket.Conn and receive
|
|
// http.Response of handshake result.
|
|
// Should not be nil.
|
|
//
|
|
// You can use websocket.DefaultDialer or websocket.Dialer, or provide
|
|
// custom implementation.
|
|
WebsocketDialer WebsocketDialer
|
|
|
|
// Reporter is used to report failures.
|
|
// Should not be nil.
|
|
//
|
|
// You can use AssertReporter, RequireReporter (they use testify),
|
|
// or testing.TB, or provide custom implementation.
|
|
Reporter Reporter
|
|
|
|
// Printers are used to print requests and responses.
|
|
// May be nil.
|
|
//
|
|
// You can use CompactPrinter, DebugPrinter, CurlPrinter, or provide
|
|
// custom implementation.
|
|
//
|
|
// You can also use builtin printers with alternative Logger if
|
|
// you're happy with their format, but want to send logs somewhere
|
|
// else instead of testing.TB.
|
|
Printers []Printer
|
|
}
|
|
|
|
// RequestFactory is used to create all http.Request objects.
|
|
// aetest.Instance from the Google App Engine implements this interface.
|
|
type RequestFactory interface {
|
|
NewRequest(method, urlStr string, body io.Reader) (*http.Request, error)
|
|
}
|
|
|
|
// Client is used to send http.Request and receive http.Response.
|
|
// http.Client implements this interface.
|
|
//
|
|
// Binder and FastBinder may be used to obtain this interface implementation.
|
|
//
|
|
// Example:
|
|
// httpBinderClient := &http.Client{
|
|
// Transport: httpexpect.NewBinder(HTTPHandler),
|
|
// }
|
|
// fastBinderClient := &http.Client{
|
|
// Transport: httpexpect.NewFastBinder(FastHTTPHandler),
|
|
// }
|
|
type Client interface {
|
|
// Do sends request and returns response.
|
|
Do(*http.Request) (*http.Response, error)
|
|
}
|
|
|
|
// WebsocketDialer is used to establish websocket.Conn and receive http.Response
|
|
// of handshake result.
|
|
// websocket.Dialer implements this interface.
|
|
//
|
|
// NewWebsocketDialer and NewFastWebsocketDialer may be used to obtain this
|
|
// interface implementation.
|
|
//
|
|
// Example:
|
|
// e := httpexpect.WithConfig(httpexpect.Config{
|
|
// BaseURL: "http://example.com",
|
|
// WebsocketDialer: httpexpect.NewWebsocketDialer(myHandler),
|
|
// })
|
|
type WebsocketDialer interface {
|
|
// Dial establishes new WebSocket connection and returns response
|
|
// of handshake result.
|
|
Dial(url string, reqH http.Header) (*websocket.Conn, *http.Response, error)
|
|
}
|
|
|
|
// Printer is used to print requests and responses.
|
|
// CompactPrinter, DebugPrinter, and CurlPrinter implement this interface.
|
|
type Printer interface {
|
|
// Request is called before request is sent.
|
|
Request(*http.Request)
|
|
|
|
// Response is called after response is received.
|
|
Response(*http.Response, time.Duration)
|
|
}
|
|
|
|
// WebsocketPrinter is used to print writes and reads of WebSocket connection.
|
|
//
|
|
// If WebSocket connection is used, all Printers that also implement WebsocketPrinter
|
|
// are invoked on every WebSocket message read or written.
|
|
//
|
|
// DebugPrinter implements this interface.
|
|
type WebsocketPrinter interface {
|
|
Printer
|
|
|
|
// WebsocketWrite is called before writes to WebSocket connection.
|
|
WebsocketWrite(typ int, content []byte, closeCode int)
|
|
|
|
// WebsocketRead is called after reads from WebSocket connection.
|
|
WebsocketRead(typ int, content []byte, closeCode int)
|
|
}
|
|
|
|
// Logger is used as output backend for Printer.
|
|
// testing.TB implements this interface.
|
|
type Logger interface {
|
|
// Logf writes message to log.
|
|
Logf(fmt string, args ...interface{})
|
|
}
|
|
|
|
// Reporter is used to report failures.
|
|
// testing.TB, AssertReporter, and RequireReporter implement this interface.
|
|
type Reporter interface {
|
|
// Errorf reports failure.
|
|
// Allowed to return normally or terminate test using t.FailNow().
|
|
Errorf(message string, args ...interface{})
|
|
}
|
|
|
|
// LoggerReporter combines Logger and Reporter interfaces.
|
|
type LoggerReporter interface {
|
|
Logger
|
|
Reporter
|
|
}
|
|
|
|
// DefaultRequestFactory is the default RequestFactory implementation which just
|
|
// calls http.NewRequest.
|
|
type DefaultRequestFactory struct{}
|
|
|
|
// NewRequest implements RequestFactory.NewRequest.
|
|
func (DefaultRequestFactory) NewRequest(
|
|
method, urlStr string, body io.Reader) (*http.Request, error) {
|
|
return http.NewRequest(method, urlStr, body)
|
|
}
|
|
|
|
// New returns a new Expect object.
|
|
//
|
|
// baseURL specifies URL to prepended to all request. My be empty. If non-empty,
|
|
// trailing slash is allowed but not required and is appended automatically.
|
|
//
|
|
// New is a shorthand for WithConfig. It uses:
|
|
// - CompactPrinter as Printer, with testing.TB as Logger
|
|
// - AssertReporter as Reporter
|
|
// - DefaultRequestFactory as RequestFactory
|
|
//
|
|
// Client is set to a default client with a non-nil Jar:
|
|
// &http.Client{
|
|
// Jar: httpexpect.NewJar(),
|
|
// }
|
|
//
|
|
// Example:
|
|
// func TestSomething(t *testing.T) {
|
|
// e := httpexpect.New(t, "http://example.com/")
|
|
//
|
|
// e.GET("/path").
|
|
// Expect().
|
|
// Status(http.StatusOK)
|
|
// }
|
|
func New(t LoggerReporter, baseURL string) *Expect {
|
|
return WithConfig(Config{
|
|
BaseURL: baseURL,
|
|
Reporter: NewAssertReporter(t),
|
|
Printers: []Printer{
|
|
NewCompactPrinter(t),
|
|
},
|
|
})
|
|
}
|
|
|
|
// WithConfig returns a new Expect object with given config.
|
|
//
|
|
// Reporter should not be nil.
|
|
//
|
|
// If RequestFactory is nil, it's set to a DefaultRequestFactory instance.
|
|
//
|
|
// If Client is nil, it's set to a default client with a non-nil Jar:
|
|
// &http.Client{
|
|
// Jar: httpexpect.NewJar(),
|
|
// }
|
|
//
|
|
// If WebsocketDialer is nil, it's set to a default dialer:
|
|
// &websocket.Dialer{}
|
|
//
|
|
// Example:
|
|
// func TestSomething(t *testing.T) {
|
|
// e := httpexpect.WithConfig(httpexpect.Config{
|
|
// BaseURL: "http://example.com/",
|
|
// Client: &http.Client{
|
|
// Transport: httpexpect.NewBinder(myHandler()),
|
|
// Jar: httpexpect.NewJar(),
|
|
// },
|
|
// Reporter: httpexpect.NewAssertReporter(t),
|
|
// Printers: []httpexpect.Printer{
|
|
// httpexpect.NewCurlPrinter(t),
|
|
// httpexpect.NewDebugPrinter(t, true)
|
|
// },
|
|
// })
|
|
//
|
|
// e.GET("/path").
|
|
// Expect().
|
|
// Status(http.StatusOK)
|
|
// }
|
|
func WithConfig(config Config) *Expect {
|
|
if config.Reporter == nil {
|
|
panic("config.Reporter is nil")
|
|
}
|
|
if config.RequestFactory == nil {
|
|
config.RequestFactory = DefaultRequestFactory{}
|
|
}
|
|
if config.Client == nil {
|
|
config.Client = &http.Client{
|
|
Jar: NewJar(),
|
|
}
|
|
}
|
|
if config.WebsocketDialer == nil {
|
|
config.WebsocketDialer = &websocket.Dialer{}
|
|
}
|
|
return &Expect{
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
// NewJar returns a new http.CookieJar.
|
|
//
|
|
// Returned jar is implemented in net/http/cookiejar. PublicSuffixList is
|
|
// implemented in golang.org/x/net/publicsuffix.
|
|
//
|
|
// Note that this jar ignores cookies when request url is empty.
|
|
func NewJar() http.CookieJar {
|
|
jar, err := cookiejar.New(&cookiejar.Options{
|
|
PublicSuffixList: publicsuffix.List,
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return jar
|
|
}
|
|
|
|
// Builder returns a copy of Expect instance with given builder attached to it.
|
|
// Returned copy contains all previously attached builders plus a new one.
|
|
// Builders are invoked from Request method, after constructing every new request.
|
|
//
|
|
// Example:
|
|
// e := httpexpect.New(t, "http://example.com")
|
|
//
|
|
// token := e.POST("/login").WithForm(Login{"ford", "betelgeuse7"}).
|
|
// Expect().
|
|
// Status(http.StatusOK).JSON().Object().Value("token").String().Raw()
|
|
//
|
|
// auth := e.Builder(func (req *httpexpect.Request) {
|
|
// req.WithHeader("Authorization", "Bearer "+token)
|
|
// })
|
|
//
|
|
// auth.GET("/restricted").
|
|
// Expect().
|
|
// Status(http.StatusOK)
|
|
func (e *Expect) Builder(builder func(*Request)) *Expect {
|
|
ret := *e
|
|
ret.builders = append(e.builders, builder)
|
|
return &ret
|
|
}
|
|
|
|
// Matcher returns a copy of Expect instance with given matcher attached to it.
|
|
// Returned copy contains all previously attached matchers plus a new one.
|
|
// Matchers are invoked from Request.Expect method, after retrieving a new response.
|
|
//
|
|
// Example:
|
|
// e := httpexpect.New(t, "http://example.com")
|
|
//
|
|
// m := e.Matcher(func (resp *httpexpect.Response) {
|
|
// resp.Header("API-Version").NotEmpty()
|
|
// })
|
|
//
|
|
// m.GET("/some-path").
|
|
// Expect().
|
|
// Status(http.StatusOK)
|
|
//
|
|
// m.GET("/bad-path").
|
|
// Expect().
|
|
// Status(http.StatusNotFound)
|
|
func (e *Expect) Matcher(matcher func(*Response)) *Expect {
|
|
ret := *e
|
|
ret.matchers = append(e.matchers, matcher)
|
|
return &ret
|
|
}
|
|
|
|
// Request returns a new Request object.
|
|
// Arguments a similar to NewRequest.
|
|
// After creating request, all builders attached to Expect object are invoked.
|
|
// See Builder.
|
|
func (e *Expect) Request(method, path string, pathargs ...interface{}) *Request {
|
|
req := NewRequest(e.config, method, path, pathargs...)
|
|
|
|
for _, builder := range e.builders {
|
|
builder(req)
|
|
}
|
|
|
|
for _, matcher := range e.matchers {
|
|
req.WithMatcher(matcher)
|
|
}
|
|
|
|
return req
|
|
}
|
|
|
|
// OPTIONS is a shorthand for e.Request("OPTIONS", path, pathargs...).
|
|
func (e *Expect) OPTIONS(path string, pathargs ...interface{}) *Request {
|
|
return e.Request("OPTIONS", path, pathargs...)
|
|
}
|
|
|
|
// HEAD is a shorthand for e.Request("HEAD", path, pathargs...).
|
|
func (e *Expect) HEAD(path string, pathargs ...interface{}) *Request {
|
|
return e.Request("HEAD", path, pathargs...)
|
|
}
|
|
|
|
// GET is a shorthand for e.Request("GET", path, pathargs...).
|
|
func (e *Expect) GET(path string, pathargs ...interface{}) *Request {
|
|
return e.Request("GET", path, pathargs...)
|
|
}
|
|
|
|
// POST is a shorthand for e.Request("POST", path, pathargs...).
|
|
func (e *Expect) POST(path string, pathargs ...interface{}) *Request {
|
|
return e.Request("POST", path, pathargs...)
|
|
}
|
|
|
|
// PUT is a shorthand for e.Request("PUT", path, pathargs...).
|
|
func (e *Expect) PUT(path string, pathargs ...interface{}) *Request {
|
|
return e.Request("PUT", path, pathargs...)
|
|
}
|
|
|
|
// PATCH is a shorthand for e.Request("PATCH", path, pathargs...).
|
|
func (e *Expect) PATCH(path string, pathargs ...interface{}) *Request {
|
|
return e.Request("PATCH", path, pathargs...)
|
|
}
|
|
|
|
// DELETE is a shorthand for e.Request("DELETE", path, pathargs...).
|
|
func (e *Expect) DELETE(path string, pathargs ...interface{}) *Request {
|
|
return e.Request("DELETE", path, pathargs...)
|
|
}
|
|
|
|
// Value is a shorthand for NewValue(e.config.Reporter, value).
|
|
func (e *Expect) Value(value interface{}) *Value {
|
|
return NewValue(e.config.Reporter, value)
|
|
}
|
|
|
|
// Object is a shorthand for NewObject(e.config.Reporter, value).
|
|
func (e *Expect) Object(value map[string]interface{}) *Object {
|
|
return NewObject(e.config.Reporter, value)
|
|
}
|
|
|
|
// Array is a shorthand for NewArray(e.config.Reporter, value).
|
|
func (e *Expect) Array(value []interface{}) *Array {
|
|
return NewArray(e.config.Reporter, value)
|
|
}
|
|
|
|
// String is a shorthand for NewString(e.config.Reporter, value).
|
|
func (e *Expect) String(value string) *String {
|
|
return NewString(e.config.Reporter, value)
|
|
}
|
|
|
|
// Number is a shorthand for NewNumber(e.config.Reporter, value).
|
|
func (e *Expect) Number(value float64) *Number {
|
|
return NewNumber(e.config.Reporter, value)
|
|
}
|
|
|
|
// Boolean is a shorthand for NewBoolean(e.config.Reporter, value).
|
|
func (e *Expect) Boolean(value bool) *Boolean {
|
|
return NewBoolean(e.config.Reporter, value)
|
|
}
|