End-to-end HTTP and REST API testing for Go.

Overview

httpexpect GoDev Build Coveralls GitHub release

Concise, declarative, and easy to use end-to-end HTTP and REST API testing for Go (golang).

Basically, httpexpect is a set of chainable builders for HTTP requests and assertions for HTTP responses and payload, on top of net/http and several utility packages.

Workflow:

  • Incrementally build HTTP requests.
  • Inspect HTTP responses.
  • Inspect response payload recursively.

Features

Request builder
Response assertions
  • Response status, predefined status ranges.
  • Headers, cookies, payload: JSON, JSONP, forms, text.
  • Round-trip time.
  • Custom reusable response matchers.
Payload assertions
  • Type-specific assertions, supported types: object, array, string, number, boolean, null, datetime.
  • Regular expressions.
  • Simple JSON queries (using subset of JSONPath), provided by jsonpath package.
  • JSON Schema validation, provided by gojsonschema package.
WebSocket support (thanks to @tyranron)
  • Upgrade an HTTP connection to a WebSocket connection (we use gorilla/websocket internally).
  • Interact with the WebSocket server.
  • Inspect WebSocket connection parameters and WebSocket messages.
Pretty printing
  • Verbose error messages.
  • JSON diff is produced on failure using gojsondiff package.
  • Failures are reported using testify (assert or require package) or standard testing package.
  • Dumping requests and responses in various formats, using httputil, http2curl, or simple compact logger.
Tuning
  • Tests can communicate with server via real HTTP client or invoke net/http or fasthttp handler directly.
  • Custom HTTP client, logger, printer, and failure reporter may be provided by user.
  • Custom HTTP request factory may be provided, e.g. from the Google App Engine testing.

Versions

The versions are selected according to the semantic versioning scheme. Every new major version gets its own stable branch with a backwards compatibility promise. Releases are tagged from stable branches.

The current stable branch is v2. Previous branches are still maintained, but no new features are added.

If you're using go.mod, use a versioned import path:

import "github.com/gavv/httpexpect/v2"

Otherwise, use gopkg.in import path:

import "gopkg.in/gavv/httpexpect.v2"

Documentation

Documentation is available on pkg.go.dev. It contains an overview and reference.

Examples

See _examples directory for complete standalone examples.

  • fruits_test.go

    Testing a simple CRUD server made with bare net/http.

  • iris_test.go

    Testing a server made with iris framework. Example includes JSON queries and validation, URL and form parameters, basic auth, sessions, and streaming. Tests invoke the http.Handler directly.

  • echo_test.go

    Testing a server with JWT authentication made with echo framework. Tests use either HTTP client or invoke the http.Handler directly.

  • gin_test.go

    Testing a server utilizing the gin web framework. Tests invoke the http.Handler directly.

  • fasthttp_test.go

    Testing a server made with fasthttp package. Tests invoke the fasthttp.RequestHandler directly.

  • websocket_test.go

    Testing a WebSocket server based on gorilla/websocket. Tests invoke the http.Handler or fasthttp.RequestHandler directly.

  • gae_test.go

    Testing a server running under the Google App Engine.

Quick start

Hello, world!
package example

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gavv/httpexpect/v2"
)

func TestFruits(t *testing.T) {
	// create http.Handler
	handler := FruitsHandler()

	// run server using httptest
	server := httptest.NewServer(handler)
	defer server.Close()

	// create httpexpect instance
	e := httpexpect.New(t, server.URL)

	// is it working?
	e.GET("/fruits").
		Expect().
		Status(http.StatusOK).JSON().Array().Empty()
}
JSON
orange := map[string]interface{}{
	"weight": 100,
}

e.PUT("/fruits/orange").WithJSON(orange).
	Expect().
	Status(http.StatusNoContent).NoContent()

e.GET("/fruits/orange").
	Expect().
	Status(http.StatusOK).
	JSON().Object().ContainsKey("weight").ValueEqual("weight", 100)

apple := map[string]interface{}{
	"colors": []interface{}{"green", "red"},
	"weight": 200,
}

e.PUT("/fruits/apple").WithJSON(apple).
	Expect().
	Status(http.StatusNoContent).NoContent()

obj := e.GET("/fruits/apple").
	Expect().
	Status(http.StatusOK).JSON().Object()

obj.Keys().ContainsOnly("colors", "weight")

obj.Value("colors").Array().Elements("green", "red")
obj.Value("colors").Array().Element(0).String().Equal("green")
obj.Value("colors").Array().Element(1).String().Equal("red")
obj.Value("colors").Array().First().String().Equal("green")
obj.Value("colors").Array().Last().String().Equal("red")
JSON Schema and JSON Path
schema := `{
	"type": "array",
	"items": {
		"type": "object",
		"properties": {
			...
			"private": {
				"type": "boolean"
			}
		}
	}
}`

repos := e.GET("/repos/octocat").
	Expect().
	Status(http.StatusOK).JSON()

// validate JSON schema
repos.Schema(schema)

// run JSONPath query and iterate results
for _, private := range repos.Path("$..private").Array().Iter() {
	private.Boolean().False()
}
Forms
// post form encoded from struct or map
e.POST("/form").WithForm(structOrMap).
	Expect().
	Status(http.StatusOK)

// set individual fields
e.POST("/form").WithFormField("foo", "hello").WithFormField("bar", 123).
	Expect().
	Status(http.StatusOK)

// multipart form
e.POST("/form").WithMultipart().
	WithFile("avatar", "./john.png").WithFormField("username", "john").
	Expect().
	Status(http.StatusOK)
URL construction
// construct path using ordered parameters
e.GET("/repos/{user}/{repo}", "octocat", "hello-world").
	Expect().
	Status(http.StatusOK)

// construct path using named parameters
e.GET("/repos/{user}/{repo}").
	WithPath("user", "octocat").WithPath("repo", "hello-world").
	Expect().
	Status(http.StatusOK)

// set query parameters
e.GET("/repos/{user}", "octocat").WithQuery("sort", "asc").
	Expect().
	Status(http.StatusOK)    // "/repos/octocat?sort=asc"
Headers
// set If-Match
e.POST("/users/john").WithHeader("If-Match", etag).WithJSON(john).
	Expect().
	Status(http.StatusOK)

// check ETag
e.GET("/users/john").
	Expect().
	Status(http.StatusOK).Header("ETag").NotEmpty()

// check Date
t := time.Now()

e.GET("/users/john").
	Expect().
	Status(http.StatusOK).Header("Date").DateTime().InRange(t, time.Now())
Cookies
// set cookie
t := time.Now()

e.POST("/users/john").WithCookie("session", sessionID).WithJSON(john).
	Expect().
	Status(http.StatusOK)

// check cookies
c := e.GET("/users/john").
	Expect().
	Status(http.StatusOK).Cookie("session")

c.Value().Equal(sessionID)
c.Domain().Equal("example.com")
c.Path().Equal("/")
c.Expires().InRange(t, t.Add(time.Hour * 24))
Regular expressions
// simple match
e.GET("/users/john").
	Expect().
	Header("Location").
	Match("http://(.+)/users/(.+)").Values("example.com", "john")

// check capture groups by index or name
m := e.GET("/users/john").
	Expect().
	Header("Location").Match("http://(?P<host>.+)/users/(?P<user>.+)")

m.Index(0).Equal("http://example.com/users/john")
m.Index(1).Equal("example.com")
m.Index(2).Equal("john")

m.Name("host").Equal("example.com")
m.Name("user").Equal("john")
Subdomains and per-request URL
e.GET("/path").WithURL("http://example.com").
	Expect().
	Status(http.StatusOK)

e.GET("/path").WithURL("http://subdomain.example.com").
	Expect().
	Status(http.StatusOK)
WebSocket support
ws := e.GET("/mysocket").WithWebsocketUpgrade().
	Expect().
	Status(http.StatusSwitchingProtocols).
	Websocket()
defer ws.Disconnect()

ws.WriteText("some request").
	Expect().
	TextMessage().Body().Equal("some response")

ws.CloseWithText("bye").
	Expect().
	CloseMessage().NoContent()
Reusable builders
e := httpexpect.New(t, "http://example.com")

r := e.POST("/login").WithForm(Login{"ford", "betelgeuse7"}).
	Expect().
	Status(http.StatusOK).JSON().Object()

token := r.Value("token").String().Raw()

auth := e.Builder(func (req *httpexpect.Request) {
	req.WithHeader("Authorization", "Bearer "+token)
})

auth.GET("/restricted").
	Expect().
	Status(http.StatusOK)

e.GET("/restricted").
	Expect().
	Status(http.StatusUnauthorized)
Reusable matchers
e := httpexpect.New(t, "http://example.com")

// every response should have this header
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)
Request transformers
e := httpexpect.New(t, "http://example.com")

myTranform := func(r* http.Request) {
	// modify the underlying http.Request
}

// apply transformer to a single request
e.POST("/some-path").
	WithTransformer(myTranform).
	Expect().
	Status(http.StatusOK)

// create a builder that applies transfromer to every request
myBuilder := e.Builder(func (req *httpexpect.Request) {
	req.WithTransformer(myTranform)
})

myBuilder.POST("/some-path").
	Expect().
	Status(http.StatusOK)
Custom config
e := httpexpect.WithConfig(httpexpect.Config{
	// prepend this url to all requests
	BaseURL: "http://example.com",

	// use http.Client with a cookie jar and timeout
	Client: &http.Client{
		Jar:     httpexpect.NewJar(),
		Timeout: time.Second * 30,
	},

	// use fatal failures
	Reporter: httpexpect.NewRequireReporter(t),

	// use verbose logging
	Printers: []httpexpect.Printer{
		httpexpect.NewCurlPrinter(t),
		httpexpect.NewDebugPrinter(t, true),
	},
})
Use HTTP handler directly
// invoke http.Handler directly using httpexpect.Binder
var handler http.Handler = MyHandler()

e := httpexpect.WithConfig(httpexpect.Config{
	Reporter: httpexpect.NewAssertReporter(t),
	Client: &http.Client{
		Transport: httpexpect.NewBinder(handler),
		Jar:       httpexpect.NewJar(),
	},
})

// invoke fasthttp.RequestHandler directly using httpexpect.FastBinder
var handler fasthttp.RequestHandler = myHandler()

e := httpexpect.WithConfig(httpexpect.Config{
	Reporter: httpexpect.NewAssertReporter(t),
	Client: &http.Client{
		Transport: httpexpect.NewFastBinder(handler),
		Jar:       httpexpect.NewJar(),
	},
})
Per-request client or handler
e := httpexpect.New(t, server.URL)

client := &http.Client{
	Transport: &http.Transport{
		DisableCompression: true,
	},
}

// overwrite client
e.GET("/path").WithClient(client).
	Expect().
	Status(http.StatusOK)

// construct client that invokes a handler directly and overwrite client
e.GET("/path").WithHandler(handler).
	Expect().
	Status(http.StatusOK)
Session support
// cookie jar is used to store cookies from server
e := httpexpect.WithConfig(httpexpect.Config{
	Reporter: httpexpect.NewAssertReporter(t),
	Client: &http.Client{
		Jar: httpexpect.NewJar(), // used by default if Client is nil
	},
})

// cookies are disabled
e := httpexpect.WithConfig(httpexpect.Config{
	Reporter: httpexpect.NewAssertReporter(t),
	Client: &http.Client{
		Jar: nil,
	},
})
TLS support
// use TLS with http.Transport
e := httpexpect.WithConfig(httpexpect.Config{
	Reporter: httpexpect.NewAssertReporter(t),
	Client: &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				// accept any certificate; for testing only!
				InsecureSkipVerify: true,
			},
		},
	},
})

// use TLS with http.Handler
e := httpexpect.WithConfig(httpexpect.Config{
	Reporter: httpexpect.NewAssertReporter(t),
	Client: &http.Client{
		Transport: &httpexpect.Binder{
			Handler: myHandler,
			TLS:     &tls.ConnectionState{},
		},
	},
})

Similar packages

Contributing

Feel free to report bugs, suggest improvements, and send pull requests! Please add documentation and tests for new features.

Update dependencies, build code, and run tests and linters:

$ make

Format code:

$ make fmt

License

MIT

Issues
  • Add Google App Engine Testing Instance support (with RequestFactory interface{})

    Add Google App Engine Testing Instance support (with RequestFactory interface{})

    This PR adds support for Google App Engine Testing.

    Background

    Each *http.Request needs to be registered with the GAE test instance. Otherwise calls to appengine.NewContext(*http.Request) will panic with:

    appengine: NewContext passed an unknown http.Request
    

    References

    • https://github.com/golang/appengine/blob/master/aetest/instance_vm.go#L60
    • https://cloud.google.com/appengine/docs/go/tools/localunittesting/

    Changes

    • Add GaeTestInstance to struct Config
    • If Config.GaeTestInstance is specified (not nil), use GaeTestInstance.NewRequest to create *http.Request that is associated with the GAE test instance.
    • Otherwise create plain *http.Request.
    • In order to keep the correct reference, struct Request needs pointer to request.
    opened by mattes 14
  • Update to Iris version 12

    Update to Iris version 12

    Hello @gavv how are you?

    Iris has been updated to version 12.0.1 which requires a new semantic import path. Read here for more. This PR updates the [_examples/iris.go](_examples] to be compatible with Iris v12.0.1.

    Thanks, Gerasimos Maropoulos. Author of the Iris Web Framework.

    opened by kataras 11
  • Session support

    Session support

    Hi!

    First-off: awesome framework.

    Secondly, I have noticed that there's no support for session/cookie-data?

    I can login using

        e.POST("/login/").WithForm(data).
            Expect().
            Status(http.StatusFound)
    

    But on successive calls, the retrieved cookie is not being used. Seeing as most pages will require some kind of authentication, I would expect this feature to exist? (either now or in the future)

    enhancement 
    opened by EtienneBruines 11
  • I have issue when testing subdomains

    I have issue when testing subdomains

    Hello my friend, I have some issues when testing subdomains, the same time the browser returns status 200 ( with cleaned cache) the httpexpect returns 404, here is example code:

    
    const (
        scheme = "http://"
        domain = "mydomain.com"
        port   = 8080 // this will go as test flag some day.
    )
    
    var (
        addr = domain + ":" + strconv.Itoa(port)
        host = scheme + addr
    )
    
    // tester_lister starts the server to listen on, this is useful ONLY WHEN TEST (AND) SUBDOMAINS,
    // the hosts file (on windows) must be setted as '127.0.0.1 mydomain.com' & '127.0.0.1 mysubdomain.mydomain.com'
    func tester_listen(api *iris.Framework, t *testing.T) *httpexpect.Expect {
        api.Config.DisableBanner = true
        go api.Listen(addr)
        time.Sleep(time.Duration(10) * time.Second)
        return httpexpect.WithConfig(httpexpect.Config{
            BaseURL: host,
            Client: &http.Client{
                Transport: httpexpect.NewFastBinder(api.HTTPServer.Handler),
                Jar:       httpexpect.NewJar(),
            },
            Reporter: httpexpect.NewAssertReporter(t),
            Printers: []httpexpect.Printer{
                httpexpect.NewDebugPrinter(t, true),
            },
        })
    }
    
    
    // and the test:
            api := iris.New()
    //...
        mysubdomain := api.Party("mysubdomain.").Get("/get", func(ctx *iris.Context) {
            writeValues(ctx)
        })
        h := tester_listen(api, t)
    //...
                h.Request("GET", scheme+"mysubdomain."+addr+"/get").Expect().Status(iris.StatusOK).JSON().Object().Equal(values)
        //...
    
    

    As you see I put a real server running & time.Sleep of 10 seconds in order to have time to test it on my browser, there the subdomain worked, at the tests it shows me:

    printer.go:55: GET http://mysubdomain.mydomain.com:8080/get HTTP/1.1
                    Host: mydomain.com:8080
    
            printer.go:72: HTTP/0.0 404 Not Found 0
                    Content-Type: text/plain; charset=utf-8
    
                    Not Found
            Error Trace:    reporter.go:22
                            chain.go:21
                            response.go:437
                            response.go:123
                            sessions_test.go:63
            Error:
                            expected status equal to:
                             "200 OK"
    
                            but got:
                             "404 Not Found"
    
    

    Complete code:

    // file ./test/iris_test.go
    //Package test -v ./... builds all tests
    package test
    
    import (
        "net/http"
        "strconv"
        "testing"
    
        "github.com/gavv/httpexpect"
        "github.com/kataras/iris"
    )
    
    const (
        scheme = "http://"
        domain = "mydomain.com"
        port   = 8080 // this will go as test flag some day.
    
        // will start  the server to real listen , this is useful ONLY WHEN TEST (AND) SUBDOMAINS,
        // the hosts file (on windows) must be setted as '127.0.0.1 mydomain.com' & '127.0.0.1 mysubdomain.mydomain.com'
        enable_subdomains_tests = true // this will go as test flag some day also.
        subdomain               = "mysubdomain"
    )
    
    var (
        addr           = domain + ":" + strconv.Itoa(port)
        host           = scheme + addr
        subdomain_host = scheme + subdomain + "." + addr
    )
    
    func tester(api *iris.Framework, t *testing.T) *httpexpect.Expect {
        api.Config.DisableBanner = true
        go func() { // no need goroutine here, we could just add go api.Listen(addr) but newcomers can see easier that these will run in a non-blocking way
            if enable_subdomains_tests {
                api.Listen(addr)
            } else {
                api.NoListen(addr)
            }
        }()
    
        if ok := <-api.Available; !ok {
            panic("Unexpected error: server cannot start, please report this as bug!!")
        }
        close(api.Available)
    
        handler := api.HTTPServer.Handler
        return httpexpect.WithConfig(httpexpect.Config{
            BaseURL: host,
            Client: &http.Client{
                Transport: httpexpect.NewFastBinder(handler),
                Jar:       httpexpect.NewJar(),
            },
            Reporter: httpexpect.NewAssertReporter(t),
            Printers: []httpexpect.Printer{
                httpexpect.NewDebugPrinter(t, true),
            },
        })
    
    }
    
    
    //file ./test/sessions_test.go
    
    //Package test -v ./... builds all tests
    package test
    
    import (
        "testing"
    
        "github.com/kataras/iris"
    )
    
    func TestSessions(t *testing.T) {
        sessionId := "mycustomsessionid"
    
        values := map[string]interface{}{
            "Name":   "iris",
            "Months": "4",
            "Secret": "dsads£2132215£%%Ssdsa",
        }
    
        api := iris.New()
    
        api.Config.Sessions.Cookie = sessionId
        writeValues := func(ctx *iris.Context) {
            sessValues := ctx.Session().GetAll()
            ctx.JSON(iris.StatusOK, sessValues)
        }
    
        api.Post("set", func(ctx *iris.Context) {
            vals := make(map[string]interface{}, 0)
            if err := ctx.ReadJSON(&vals); err != nil {
                t.Fatalf("Cannot readjson. Trace %s", err.Error())
            }
            for k, v := range vals {
                ctx.Session().Set(k, v)
            }
        })
    
        api.Get("/get", func(ctx *iris.Context) {
            writeValues(ctx)
        })
    
        if enable_subdomains_tests {
            api.Party(subdomain+".").Get("/get", func(ctx *iris.Context) {
                writeValues(ctx)
            })
        }
    
        api.Get("/clear", func(ctx *iris.Context) {
            ctx.Session().Clear()
            writeValues(ctx)
        })
    
        api.Get("/destroy", func(ctx *iris.Context) {
            ctx.SessionDestroy()
            writeValues(ctx)
            // the cookie and all values should be empty
        })
    
        h := tester(api, t)
    
        h.POST("/set").WithJSON(values).Expect().Status(iris.StatusOK).Cookies().NotEmpty()
        h.GET("/get").Expect().Status(iris.StatusOK).JSON().Object().Equal(values)
        if enable_subdomains_tests {
            h.Request("GET", subdomain_host+"/get").Expect().Status(iris.StatusOK).JSON().Object().Equal(values)
        }
    
        // test destory which also clears first
        d := h.GET("/destroy").Expect().Status(iris.StatusOK)
        d.JSON().Object().Empty()
        d.Cookies().ContainsOnly(sessionId)
        // set and clear again
        h.POST("/set").WithJSON(values).Expect().Status(iris.StatusOK).Cookies().NotEmpty()
        h.GET("/clear").Expect().Status(iris.StatusOK).JSON().Object().Empty()
    
    }
    

    When enable_subdomains_tests is false the tests are passed*


    Thoughts: If I remove the BaseURL entirely I have two problems:

    • the h.GET(host + "/get").Expect().Status(iris.StatusOK).JSON().Object().Equal(values) returns empty json response , while it's not empty. Solution: if you check manually the domain field of a cookie header, keep note of the RFC2109, meaning that a domain field can be filled as .mydomain.com that's means that the mydomain.com AND all first-level subdomains have access to this cookie , iris sets the domain field to "."+domain in order to achieve the sessions persistence on subdomains, which is working well on all browsers)
    • I have to put the http://mydomain.com in frontof all the .GET/POST... and it is not a good idea, I think the BaseURL takes the host and all requests are declared as relative to it. Solution: you could check if the 'path' starts with http://, https://, ws:// then it is url and not a BaseURL's relative path.

    If you want to run the code by yourself or add some httpexpect fixes so I can learn from those, do that in iris-contrib/tests ( I updated the tests), thanks!!

    enhancement 
    opened by ghost 10
  • Add more unit tests for WebSockets

    Add more unit tests for WebSockets

    WebSocket support is covered with a set of integration tests, but only a few unit tests: 1, 2.

    The test coverage is incomplete, see websocket* files here: https://coveralls.io/github/gavv/httpexpect?branch=master

    It would be nice to add more unit tests and increase test coverage. In particular, most assertion failures and some message types are not covered currently.

    tests help wanted 
    opened by gavv 9
  • Unify and improve error reporting

    Unify and improve error reporting

    • Unify error formatting

      Currently, every component do its own error message formatting. Instead, all of them should fill a struct with error info and pass it to error formatter, which will format it as text and pass to error reporter. The user can provide their own error formatter.

    • Improve error messages

      The error message should include:

      • pretty-printed request
      • pretty-printed response
      • round-trip time
      • error message
      • pretty-printed expected value
      • pretty-printed actual value
      • diff
      • backtrace
    • Use colored output

      When running in a TTY, use colors for error message and diff (gojsondiff allows that). The user can enable, disable, or choose an automatic mode for this.

    duplicate 
    opened by gavv 9
  • Basic WebSocket support

    Basic WebSocket support

    Resolves #50

    Why this PR is here?

    I haven't found any handy solutions for E2E WebSockets testing on Go. The only solution I've found is github.com/posener/wstest which is for unit tests only, however.

    What does this PR do?

    Adds first-class library support for WebSocket testing. Built on top of github.com/gorilla/websocket library, which is de facto Go standard for WebSockets at the moment.

    This supports both real WebSocket server implemented in any language (so allows E2E tests) and http.Handler / fasthttp.RequestHandler directly without running server (so allows unit tests).

    Features

    • Send WS HTTP request: req := e.WS("/my/path/to/ws")
    • Obtain WS connection from response: conn := req.Expect().Connection()
    • Close WS connection normally/abnormally: conn.Close() / conn.Disconnect()
    • Write to WS connection: conn.WriteText("hi"), conn.WriteJSON(object), conn.WriteBinary(bytes), and so on...
    • Read from WS connection: msg := conn.Expect()
    • Binder / FastBinder implementations for websocket.Dialer (to test handlers directly without running WS server)
    • WsPrinter interface extension for Printer which prints sent and received WebSocket messages (implemented for DebugPrinter)

    Example

    func TestEchoServer(t *testing.T) {
    	e := httpexpect.New(t, "http://echo.server/")
    
    	conn := e.WS("/endpoint/ws").
    		Expect().
    		Status(http.StatusSwitchingProtocols).
    		Connection()
    	defer conn.Disconnect()
    
    	conn.WriteText("hi").
    		Expect().Text().Body().Equal("hi")
    
    	conn.WriteJSON(struct {
    		Message string `json:"message"`
    	}{"hi"}).
    		Expect().
    		Text().JSON().Object().ValueEqual("message", "hi")
    
    	conn.CloseWithText("bye")
    }
    

    If you want to try it now

    Modify your Gopkg.toml:

    [[constraint]]
      name = "github.com/gavv/httpexpect"
      branch = "ws-support"
      source = "github.com/instrumentisto/httpexpect"
    
    opened by tyranron 8
  • How to use fastbind with HTTPS

    How to use fastbind with HTTPS

    I'm tiring to test a https only server on fasthttp (http request will be redirect to https). I'm following the iris example.

    My handler look like this:

    return httpexpect.WithConfig(httpexpect.Config{
            BaseURL: "https://example.com",
            Client: &http.Client{
                Transport: httpexpect.NewFastBinder(handler),
                Jar:       httpexpect.NewJar(),
            },
            Reporter: httpexpect.NewAssertReporter(t),
            Printers: []httpexpect.Printer{
                httpexpect.NewCurlPrinter(t),
                httpexpect.NewDebugPrinter(t, true),
            },
        })
    

    But when i use:

        result := func() {
                    e.GET("/").
                        Expect().
                        Status(http.StatusOK)
    

    I get 10 redirect from http to https page and test fail.

    On normal browser the redirect from http uri to https uri works well without cycling redirect.

    Is possible to test fasthttp request on https?

    enhancement 
    opened by acidvertigo 8
  • Can't read form value with iris(fasthttp)

    Can't read form value with iris(fasthttp)

    I edit iris test code from your example:

    iris.go

    func IrisHandler() fasthttp.RequestHandler {
        iris.Post("/hello", func(ctx *iris.Context) {
            ctx.SetStatusCode(iris.StatusOK)
            ctx.SetContentType("application/x-www-form-urlencoded")
    
            ctx.SetBodyString(string(ctx.FormValue("Test")))
        })
    
        return iris.NoListen().Handler
    }
    

    iris_test.go

    func TestIris(t *testing.T) {
        // create fasthttp.RequestHandler
        handler := IrisHandler()
    
        // create httpexpect instance that will call fasthtpp.RequestHandler directly
        e := httpexpect.WithConfig(httpexpect.Config{
            Reporter: httpexpect.NewAssertReporter(t),
            Client:   httpexpect.NewFastBinder(handler),
        })
    
        // run tests
        ee := e.POST("/hello").WithFormField("Test", "TESTDATA").Expect()
        ee.Status(http.StatusOK)
        ee.Form().ContainsKey("Test")
        ee.Form().ValueEqual("Test", "TESTDATA")
    }
    

    and I got the error

    --- FAIL: TestIris (0.00s)
            Error Trace:    reporter.go:22
                chain.go:21
                object.go:152
                iris_test.go:24
        Error:
                expected object containing key 'Test', but got:
                 {}
    
            Error Trace:    reporter.go:22
                chain.go:21
                object.go:236
                iris_test.go:25
        Error:
                expected object containing key 'Test', but got:
                 {}
    
    FAIL
    exit status 1
    
    bug 
    opened by gosilent 8
  • Looking for feedback for upcoming httpexpect.v2

    Looking for feedback for upcoming httpexpect.v2

    Problems with current API

    The big problem with current API is that it's totally unclear from the source code what is the type of every object in a chained call.

    Here is an example:

    e := httpexpect.New(t, "http://example.com")
    
    e.GET("/fruits/apple").                   // returns new httpexpect.Request
        WithHeader("If-Match", "something").  // returns method receiver (httpexpect.Request)
        WithQuery("key", "value").            // returns method receiver (httpexpect.Request)
        Expect().                             // returns new httpexpect.Response
        Status(http.StatusOK).                // returns method receiver (httpexpect.Response)
        ContentType("application/json").      // returns method receiver (httpexpect.Response)
        JSON().                               // returns new httpexpect.Value
        Object().                             // returns new httpexpect.Object
        ContainsKey("colors").                // returns method receiver (httpexpect.Object)
        Value("colors").                      // returns new httpexpect.Value
        Array().                              // returns new httpexpect.Array
        Element(0).                           // returns new httpexpect.Value
        String().                             // returns new httpexpect.String
        Equal("green")                        // returns method receiver (httpexpect.String)
    

    Actually there are two kind of methods: ones that return the method receiver, and ones that return a new object for a nested value. However, it's hard to recognize what is the kind of a given method, and the user should consult the documentation or IDE for each one.

    This makes the code unclear for those who are unfamiliar with the httpexpect API. People already mentioned this on reddit when httpexpect.v1 was announced there.

    Suggested new API

    I finally came up to an idea (actually not a new one) of the new API that will solve the problem. The new suggested API is based on lambdas. Less talk, more code. Here is the above example ported to new API:

    e := hx.New(t, "http://example.com")
    
    e.GET("/fruits/apple").
        WithHeader("If-Match", "something").
        WithQuery("key", "value").
        Expect(func (hr *hx.Response) {
            hr.Status(http.StatusOK)
            hr.ContentType("application/json")
            hr.JSON(func(ho *hx.Object) {
                ho.ContainsKey("colors")
                ho.Value("colors", func(ha *hx.Array) {
                    ha.Element(0, func(hs *hx.String) {
                        hs.Equal("green")
                    })
                })
            })
        })
    

    The same may be written as:

    e.GET("/fruits/apple").
        WithHeader("If-Match", "something").
        WithQuery("key", "value").
        Expect(func (hr *hx.Response) {
            hr.Status(http.StatusOK)
            hr.JSON(func(ho *hx.Object) {
                ho.Value("colors", func(ha *hx.Array) {
                    ha.ValueEqual(0, "green")
                })
            })
        })
    

    Or:

    e.GET("/fruits/apple").
        WithHeader("If-Match", "something").
        WithQuery("key", "value").
        Expect(func (hr *hx.Response) {
            hr.Status(http.StatusOK)
            hr.JSON(func(ho *hx.Object) {
                ho.Path("$..colors[0]", func(hs *hx.String) {
                    hs.Equal("green")
                })
            })
        })
    

    The new rules are dead simple:

    • every method returns its receiver
    • assertions for nested objects (response body, map value, array element) accept function as an argument

    Advantages:

    • the objects types are obvious
    • the object nesting is reflected in the source code
    • the order of statements at the same nesting level doesn't matter

    Disadvantages:

    • the compatibility is broken
    • increased usage of reflection in API (but not in user code)
    • the code looks fancier

    Since the new API forces to use package name much more often, it will be changed from httpexpect to hx (github.com/gavv/httpexpect/hx).

    Migration process

    • The httpexpect.v1 stable version (available on gopkg) will not be dropped and the compatibility will be preserved. I will not develop new features for it, but I will fix bugs and accepts pull requests.

    • The master will be switched to a new API when it will be ready and usable. I didn't start working on the new API yet, so it will take some time.

    • To allow incremental migration, every object in the new API will provide Compat() method returning an appropriate instance from the old API. Thus you can still use parts of code written for the old API.

    • When the new API will be stabilized, the Compat() methods will be removed and the httpexpect.v2 stable branch will be created.

    If you're currently using master (github.com/gavv/httpexpect), it's time to switch to a stable branch (gopkg.in/gavv/httpexpect.v1) to avoid breaks in near future.

    Other (minor) breaking changes in v2

    • #22
    • #35

    Looking for feedback

    Before starting working on the new API, I'm looking for any feedback and suggestions. You're welcome!

    @acidvertigo @banux @EtienneBruines @gosilent @JamesMura @joohoi @jraede @kataras @lstep @mattes @mrLSD @nullstyle @oalmali @phifty @sofixa @typekpb @yudai discussion 
    opened by gavv 7
  • Wrong module checksum

    Wrong module checksum

    Httpexpect requires module github.com/fasthttp/websocket v1.4.2. But the version 1.4.2 has been re-uploaded by its author. This has introduced some failures while using httpexpect:

    github.com/fasthttp/[email protected]: verifying module: checksum mismatch
            downloaded: h1:hTnl0DRQ9Tj4uVc4QcZCGp+mgF5mVKnijR1eNyn2waY=
            sum.golang.org: h1:AU/zSiIIAuJjBMf5o+vO0syGOnEfvZRu40xIhW/3RuM=
    
    SECURITY ERROR
    This download does NOT match the one reported by the checksum server.
    The bits may have been replaced on the origin server, or an attacker may
    have intercepted the download attempt.
    
    For more information, see 'go help module-auth'.
    

    Is it possible for you to release a new version with the last github.com/fasthttp/websocket version ? Thx.

    opened by sylvain-p85 6
  • tests: fix expected filename in TestRequestBodyMultipartFile

    tests: fix expected filename in TestRequestBodyMultipartFile

    currently TestRequestBodyMultipartFile expect the filename given by

    func (*multipartPart) FileName() string

    to be a fullpath in one of the test cases, however the documentation states that:

    the filename is passed through filepath.Base (which is platform dependent) before being returned.

    then the test fails.

    This change fix the expected filename for this test, using the same filepath.Base function.

    Fixes: #103

    opened by clobrano 3
  • [help] how to test  cors rule ?

    [help] how to test cors rule ?

    i test online example:

    	e := httpexpect.New(t, "https://api.mydomain.com")
    	request := e.Get("/api/info")
    	response := request.Expect()
    	response.Status(200)
    	log.Println(response.Body().Raw())
    

    now i want to test my cors rule, just like invoke api "https://api.mydomain.com/api/info" in "https://www.mydomain.com" domain

    how can i write the test unit? Thanks for your help.

    question 
    opened by huyinghuan 1
  • Bump github.com/gin-gonic/gin from 1.5.0 to 1.7.0 in /_examples

    Bump github.com/gin-gonic/gin from 1.5.0 to 1.7.0 in /_examples

    Bumps github.com/gin-gonic/gin from 1.5.0 to 1.7.0.

    Release notes

    Sourced from github.com/gin-gonic/gin's releases.

    Release v1.7.0

    BUGFIXES

    • fix compile error from #2572 (#2600)
    • fix: print headers without Authorization header on broken pipe (#2528)
    • fix(tree): reassign fullpath when register new node (#2366)

    ENHANCEMENTS

    • Support params and exact routes without creating conflicts (#2663)
    • chore: improve render string performance (#2365)
    • Sync route tree to httprouter latest code (#2368)
    • chore: rename getQueryCache/getFormCache to initQueryCache/initFormCa (#2375)
    • chore(performance): improve countParams (#2378)
    • Remove some functions that have the same effect as the bytes package (#2387)
    • update:SetMode function (#2321)
    • remove a unused type SecureJSONPrefix (#2391)
    • Add a redirect sample for POST method (#2389)
    • Add CustomRecovery builtin middleware (#2322)
    • binding: avoid 2038 problem on 32-bit architectures (#2450)
    • Prevent panic in Context.GetQuery() when there is no Request (#2412)
    • Add GetUint and GetUint64 method on gin.context (#2487)
    • update content-disposition header to MIME-style (#2512)
    • reduce allocs and improve the render WriteString (#2508)
    • implement ".Unwrap() error" on Error type (#2525) (#2526)
    • Allow bind with a map[string]string (#2484)
    • chore: update tree (#2371)
    • Support binding for slice/array obj [Rewrite] (#2302)
    • basic auth: fix timing oracle (#2609)
    • Add mixed param and non-param paths (port of httprouter#329) (#2663)
    • feat(engine): add trustedproxies and remoteIP (#2632)

    Improve performance

    ENHANCEMENTS

    • Improve performance: Change *sync.RWMutex to sync.RWMutex in context. #2351

    release v1.6.2

    Release Notes

    • BUGFIXES
      • fix missing initial sync.RWMutex (#2305)
    • ENHANCEMENTS
      • Add set samesite in cookie. (#2306)

    Contributors

    release v1.6.1

    ... (truncated)

    Changelog

    Sourced from github.com/gin-gonic/gin's changelog.

    Gin v1.7.0

    BUGFIXES

    • fix compile error from #2572 (#2600)
    • fix: print headers without Authorization header on broken pipe (#2528)
    • fix(tree): reassign fullpath when register new node (#2366)

    ENHANCEMENTS

    • Support params and exact routes without creating conflicts (#2663)
    • chore: improve render string performance (#2365)
    • Sync route tree to httprouter latest code (#2368)
    • chore: rename getQueryCache/getFormCache to initQueryCache/initFormCa (#2375)
    • chore(performance): improve countParams (#2378)
    • Remove some functions that have the same effect as the bytes package (#2387)
    • update:SetMode function (#2321)
    • remove a unused type SecureJSONPrefix (#2391)
    • Add a redirect sample for POST method (#2389)
    • Add CustomRecovery builtin middleware (#2322)
    • binding: avoid 2038 problem on 32-bit architectures (#2450)
    • Prevent panic in Context.GetQuery() when there is no Request (#2412)
    • Add GetUint and GetUint64 method on gin.context (#2487)
    • update content-disposition header to MIME-style (#2512)
    • reduce allocs and improve the render WriteString (#2508)
    • implement ".Unwrap() error" on Error type (#2525) (#2526)
    • Allow bind with a map[string]string (#2484)
    • chore: update tree (#2371)
    • Support binding for slice/array obj [Rewrite] (#2302)
    • basic auth: fix timing oracle (#2609)
    • Add mixed param and non-param paths (port of httprouter#329) (#2663)
    • feat(engine): add trustedproxies and remoteIP (#2632)

    Gin v1.6.3

    ENHANCEMENTS

    • Improve performance: Change *sync.RWMutex to sync.RWMutex in context. #2351

    Gin v1.6.2

    BUGFIXES

    • fix missing initial sync.RWMutex #2305

    ENHANCEMENTS

    • Add set samesite in cookie. #2306

    Gin v1.6.1

    BUGFIXES

    • Revert "fix accept incoming network connections" #2294

    ... (truncated)

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • Add a key to the fail message report

    Add a key to the fail message report

    What am I using?

    • Web framework: Gin
    • Ide: vscode

    Go version

    go 1.16+

    Repositorie

    https://github.com/snowlyg/iris-admin

    When I use https://github.com/snowlyg/httptest to test HTTP API and the HTTP response object or array data, I can't know the fail is which key.

    func (rks ResponseKeys) Test(object *httpexpect.Object) {
    	for _, rk := range rks {
    		zap_server.ZAPLOG.Info(rk.Key)
    		// to sign the test data key name
    		if rk.Value == nil {
    			continue
    		}
    		switch reflect.TypeOf(rk.Value).String() {
    		case "string":
    			if strings.ToLower(rk.Type) == "notempty" {
    				object.Value(rk.Key).String().NotEmpty()
    			} else {
    				object.Value(rk.Key).String().Equal(rk.Value.(string))
    			}
    		case "float64":
    			if strings.ToLower(rk.Type) == "ge" {
    				object.Value(rk.Key).Number().Ge(rk.Value.(float64))
    			} else {
    				object.Value(rk.Key).Number().Equal(rk.Value.(float64))
    			}
    		case "uint":
    			if strings.ToLower(rk.Type) == "ge" {
    				object.Value(rk.Key).Number().Ge(rk.Value.(uint))
    			} else {
    				object.Value(rk.Key).Number().Equal(rk.Value.(uint))
    			}
    		case "int":
    			if strings.ToLower(rk.Type) == "ge" {
    				object.Value(rk.Key).Number().Ge(rk.Value.(int))
    			} else {
    				object.Value(rk.Key).Number().Equal(rk.Value.(int))
    			}
    		case "[]base.ResponseKeys":
    			if strings.ToLower(rk.Type) == "empty" {
    				object.Value(rk.Key).Array().Empty()
    				continue
    			}
    

    image

    I will #add a key to the fail message report like below :

    image

    opened by snowlyg 0
  • Add a key to the fail message report

    Add a key to the fail message report

    What am I using?

    • Web framework: Gin
    • Ide: vscode

    Go version

    go 1.16+

    Repositorie

    https://github.com/snowlyg/iris-admin

    When I use https://github.com/snowlyg/httptest to test HTTP API and the HTTP response object or array data, I can't know the fail is which key.

    func (rks ResponseKeys) Test(object *httpexpect.Object) {
    	for _, rk := range rks {
    		zap_server.ZAPLOG.Info(rk.Key)
    		// to sign the test data key name
    		if rk.Value == nil {
    			continue
    		}
    		switch reflect.TypeOf(rk.Value).String() {
    		case "string":
    			if strings.ToLower(rk.Type) == "notempty" {
    				object.Value(rk.Key).String().NotEmpty()
    			} else {
    				object.Value(rk.Key).String().Equal(rk.Value.(string))
    			}
    		case "float64":
    			if strings.ToLower(rk.Type) == "ge" {
    				object.Value(rk.Key).Number().Ge(rk.Value.(float64))
    			} else {
    				object.Value(rk.Key).Number().Equal(rk.Value.(float64))
    			}
    		case "uint":
    			if strings.ToLower(rk.Type) == "ge" {
    				object.Value(rk.Key).Number().Ge(rk.Value.(uint))
    			} else {
    				object.Value(rk.Key).Number().Equal(rk.Value.(uint))
    			}
    		case "int":
    			if strings.ToLower(rk.Type) == "ge" {
    				object.Value(rk.Key).Number().Ge(rk.Value.(int))
    			} else {
    				object.Value(rk.Key).Number().Equal(rk.Value.(int))
    			}
    		case "[]base.ResponseKeys":
    			if strings.ToLower(rk.Type) == "empty" {
    				object.Value(rk.Key).Array().Empty()
    				continue
    			}
    

    image

    I will add a key to the fail message report like below :

    image #110

    opened by snowlyg 0
Owner
Victor Gaydov
Victor Gaydov
An always-on framework that performs end-to-end functional network testing for reachability, latency, and packet loss

Arachne Arachne is a packet loss detection system and an underperforming path detection system. It provides fast and easy active end-to-end functional

Uber Open Source 367 Jun 17, 2022
🚀🌏 Orbital is a simple end-to-end testing framework for Go

Orbital is a test framework which enables a developer to write end to end tests just like one would writing unit tests. We do this by effectively copying the testing.T API and registering tests to be run periodically on a configured schedule.

Segment 75 May 18, 2022
A simple and expressive HTTP server mocking library for end-to-end tests in Go.

mockhttp A simple and expressive HTTP server mocking library for end-to-end tests in Go. Installation go get -d github.com/americanas-go/mockhttp Exa

Americanas Go 6 Dec 19, 2021
End to end functional test and automation framework

Declarative end to end functional testing (endly) This library is compatible with Go 1.12+ Please refer to CHANGELOG.md if you encounter breaking chan

Viant, Inc 209 Jun 26, 2022
Rr-e2e-tests - Roadrunner end-to-end tests repository

RoadRunner end-to-end plugins tests License: The MIT License (MIT). Please see L

RoadRunner 1 Jan 21, 2022
Hsuan-Fuzz: REST API Fuzzing by Coverage Level Guided Blackbox Testing

Hsuan-Fuzz: REST API Fuzzing by Coverage Level Guided Blackbox Testing Architecture Usage package main import ( restAPI "github.com/iasthc/hsuan-

Chung-Hsuan Tsai 20 Jun 23, 2022
HTTP mock for Golang: record and replay HTTP/HTTPS interactions for offline testing

govcr A Word Of Warning I'm in the process of partly rewriting govcr to offer better support for cassette mutations. This is necessary because when I

Seb C 105 May 18, 2022
siusiu (suite-suite harmonics) a suite used to manage the suite, designed to free penetration testing engineers from learning and using various security tools, reducing the time and effort spent by penetration testing engineers on installing tools, remembering how to use tools.

siusiu (suite-suite harmonics) a suite used to manage the suite, designed to free penetration testing engineers from learning and using various security tools, reducing the time and effort spent by penetration testing engineers on installing tools, remembering how to use tools.

Re 242 Jun 20, 2022
A yaml data-driven testing format together with golang testing library

Specimen Yaml-based data-driven testing Specimen is a yaml data format for data-driven testing. This enforces separation between feature being tested

Design it, Run it 0 Jan 31, 2022
Extremely flexible golang deep comparison, extends the go testing package and tests HTTP APIs

go-testdeep Extremely flexible golang deep comparison, extends the go testing package. Latest news Synopsis Description Installation Functions Availab

Maxime Soulé 303 Jun 19, 2022
HTTP traffic mocking and testing made easy in Go ༼ʘ̚ل͜ʘ̚༽

gock Versatile HTTP mocking made easy in Go that works with any net/http based stdlib implementation. Heavily inspired by nock. There is also its Pyth

Tom 1.6k Jun 24, 2022
HTTP load testing tool and library. It's over 9000!

Vegeta Vegeta is a versatile HTTP load testing tool built out of a need to drill HTTP services with a constant request rate. It can be used both as a

Tomás Senart 19.8k Jun 27, 2022
Golang HTTP client testing framework

flute Golang HTTP client testing framework Presentation https://speakerdeck.com/szksh/flute-golang-http-client-testing-framework Overview flute is the

Shunsuke Suzuki 17 Apr 19, 2022
Ditto is a CLI testing tool that helps you verify if multiple HTTP endpoints have the same outputs.

Ditto is a CLI testing tool that helps you verify if multiple HTTP endpoints have the same outputs.

Cristopher 1 Nov 24, 2021
Tesuto - a little library for testing against HTTP services

tesuto import "github.com/guregu/tesuto" tesuto is a little library for testing

Greg 2 Jan 18, 2022
Client tool for testing HTTP server timeouts

HTTP timeout test client While testing Go HTTP server timeouts I wrote this little tool to help me test. It allows for slowing down header write and b

Adam Pritchard 7 May 11, 2022
API testing framework inspired by frisby-js

frisby REST API testing framework inspired by frisby-js, written in Go Proposals I'm starting to work on frisby again with the following ideas: Read s

_Hofstadter 272 Jun 13, 2022
Testing API Handler written in Golang.

Gofight API Handler Testing for Golang Web framework. Support Framework Http Handler Golang package http provides HTTP client and server implementatio

Bo-Yi Wu 402 Jun 19, 2022
A Go library help testing your RESTful API application

RESTit A Go micro-framework to help writing RESTful API integration test Package RESTit provides helps to those who want to write an integration test

RESTit 55 Nov 25, 2021