// 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) }