Repository: gavv/httpexpect (go)

Issue:
Improve usage checks in Request and Websocket
`Request` and `Websocket` have the following workflow:

* create instance
* likely, call `WithXXX()` methods multiple times
* call `Expect()` method

It is not allowed to call `WithXXX()` after `Expect()` is called. It is also not allowed to call `Expect()` more than once. However, these requirements are not documented and checked.

What we should do:

* Document these requirements in comment for `Expect`.
* Report failure if these requirements are violated with `Type` set to `AssertUsage` and an explanatory message.
* Add unit tests that cover these failure reports.

Pull request description:
Implement usage check for Expect in Request and Websocket
Fixes #162.

- [X] Add documentation for Expect in Request
- [X] Add usage checks for Expect and WithXXX in Request
- [X] Add unit tests for Expect and WithXXX in Request

Patch:
diff --git a/request.go b/request.go
index 23c0381..67d7dbb 100644
--- a/request.go
+++ b/request.go
@@ -51,9 +51,10 @@ type Request struct {
 	formbuf   *bytes.Buffer
 	multipart *multipart.Writer
 
-	bodySetter string
-	typeSetter string
-	forceType  bool
+	bodySetter   string
+	typeSetter   string
+	forceType    bool
+	expectCalled bool
 
 	wsUpgrade bool
 
@@ -203,6 +204,14 @@ func (r *Request) WithName(name string) *Request {
 	opChain := r.chain.enter("WithName()")
 	defer opChain.leave()
 
+	if opChain.failed() {
+		return r
+	}
+
+	if !r.checkOrder(opChain, "WithName()") {
+		return r
+	}
+
 	r.chain.setRequestName(name)
 
 	return r
@@ -226,6 +235,10 @@ func (r *Request) WithMatcher(matcher func(*Response)) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithMatcher()") {
+		return r
+	}
+
 	if matcher == nil {
 		opChain.fail(AssertionFailure{
 			Type: AssertUsage,
@@ -256,6 +269,10 @@ func (r *Request) WithTransformer(transform func(*http.Request)) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithTransformer()") {
+		return r
+	}
+
 	if transform == nil {
 		opChain.fail(AssertionFailure{
 			Type: AssertUsage,
@@ -292,6 +309,10 @@ func (r *Request) WithClient(client Client) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithClient()") {
+		return r
+	}
+
 	if client == nil {
 		opChain.fail(AssertionFailure{
 			Type: AssertUsage,
@@ -325,6 +346,10 @@ func (r *Request) WithHandler(handler http.Handler) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithHandler()") {
+		return r
+	}
+
 	if handler == nil {
 		opChain.fail(AssertionFailure{
 			Type: AssertUsage,
@@ -370,6 +395,10 @@ func (r *Request) WithContext(ctx context.Context) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithContext()") {
+		return r
+	}
+
 	if ctx == nil {
 		opChain.fail(AssertionFailure{
 			Type: AssertUsage,
@@ -408,6 +437,10 @@ func (r *Request) WithTimeout(timeout time.Duration) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithTimeout()") {
+		return r
+	}
+
 	r.timeout = timeout
 
 	return r
@@ -473,6 +506,10 @@ func (r *Request) WithRedirectPolicy(policy RedirectPolicy) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithRedirectPolicy()") {
+		return r
+	}
+
 	r.redirectPolicy = policy
 
 	return r
@@ -501,6 +538,10 @@ func (r *Request) WithMaxRedirects(maxRedirects int) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithMaxRedirects()") {
+		return r
+	}
+
 	if maxRedirects < 0 {
 		opChain.fail(AssertionFailure{
 			Type:   AssertValid,
@@ -565,6 +606,10 @@ func (r *Request) WithRetryPolicy(policy RetryPolicy) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithRetryPolicy()") {
+		return r
+	}
+
 	r.retryPolicy = policy
 
 	return r
@@ -593,6 +638,10 @@ func (r *Request) WithMaxRetries(maxRetries int) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithMaxRetries()") {
+		return r
+	}
+
 	if maxRetries < 0 {
 		opChain.fail(AssertionFailure{
 			Type:   AssertValid,
@@ -629,6 +678,10 @@ func (r *Request) WithRetryDelay(minDelay, maxDelay time.Duration) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithRetryDelay()") {
+		return r
+	}
+
 	if !(minDelay <= maxDelay) {
 		opChain.fail(AssertionFailure{
 			Type: AssertValid,
@@ -676,6 +729,10 @@ func (r *Request) WithWebsocketUpgrade() *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithWebsocketUpgrade()") {
+		return r
+	}
+
 	r.wsUpgrade = true
 
 	return r
@@ -703,6 +760,10 @@ func (r *Request) WithWebsocketDialer(dialer WebsocketDialer) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithWebsocketDialer()") {
+		return r
+	}
+
 	if dialer == nil {
 		opChain.fail(AssertionFailure{
 			Type: AssertUsage,
@@ -739,6 +800,10 @@ func (r *Request) WithPath(key string, value interface{}) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithPath()") {
+		return r
+	}
+
 	if value == nil {
 		opChain.fail(AssertionFailure{
 			Type: AssertUsage,
@@ -788,6 +853,10 @@ func (r *Request) WithPathObject(object interface{}) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithPathObject()") {
+		return r
+	}
+
 	if object == nil {
 		return r
 	}
@@ -881,6 +950,10 @@ func (r *Request) WithQuery(key string, value interface{}) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithQuery()") {
+		return r
+	}
+
 	if value == nil {
 		opChain.fail(AssertionFailure{
 			Type: AssertUsage,
@@ -929,6 +1002,10 @@ func (r *Request) WithQueryObject(object interface{}) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithQueryObject()") {
+		return r
+	}
+
 	if object == nil {
 		return r
 	}
@@ -991,6 +1068,10 @@ func (r *Request) WithQueryString(query string) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithQueryString()") {
+		return r
+	}
+
 	v, err := url.ParseQuery(query)
 
 	if err != nil {
@@ -1033,6 +1114,10 @@ func (r *Request) WithURL(urlStr string) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithURL()") {
+		return r
+	}
+
 	u, err := url.Parse(urlStr)
 	if err != nil {
 		opChain.fail(AssertionFailure{
@@ -1067,6 +1152,10 @@ func (r *Request) WithHeaders(headers map[string]string) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithHeaders()") {
+		return r
+	}
+
 	for k, v := range headers {
 		r.withHeader(k, v)
 	}
@@ -1088,6 +1177,10 @@ func (r *Request) WithHeader(k, v string) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithHeader()") {
+		return r
+	}
+
 	r.withHeader(k, v)
 
 	return r
@@ -1128,6 +1221,10 @@ func (r *Request) WithCookies(cookies map[string]string) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithCookies()") {
+		return r
+	}
+
 	for k, v := range cookies {
 		r.httpReq.AddCookie(&http.Cookie{
 			Name:  k,
@@ -1152,6 +1249,10 @@ func (r *Request) WithCookie(k, v string) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithCookie()") {
+		return r
+	}
+
 	r.httpReq.AddCookie(&http.Cookie{
 		Name:  k,
 		Value: v,
@@ -1178,6 +1279,10 @@ func (r *Request) WithBasicAuth(username, password string) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithBasicAuth()") {
+		return r
+	}
+
 	r.httpReq.SetBasicAuth(username, password)
 
 	return r
@@ -1197,6 +1302,10 @@ func (r *Request) WithHost(host string) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithHost()") {
+		return r
+	}
+
 	r.httpReq.Host = host
 
 	return r
@@ -1218,6 +1327,10 @@ func (r *Request) WithProto(proto string) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithProto()") {
+		return r
+	}
+
 	major, minor, ok := http.ParseHTTPVersion(proto)
 
 	if !ok {
@@ -1261,6 +1374,9 @@ func (r *Request) WithChunked(reader io.Reader) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithChunked()") {
+		return r
+	}
 	if !r.httpReq.ProtoAtLeast(1, 1) {
 		opChain.fail(AssertionFailure{
 			Type: AssertUsage,
@@ -1294,6 +1410,10 @@ func (r *Request) WithBytes(b []byte) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithBytes()") {
+		return r
+	}
+
 	if b == nil {
 		r.setBody(opChain, "WithBytes()", nil, 0, false)
 	} else {
@@ -1318,6 +1438,10 @@ func (r *Request) WithText(s string) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithText()") {
+		return r
+	}
+
 	r.setType(opChain, "WithText()", "text/plain; charset=utf-8", false)
 	r.setBody(opChain, "WithText()", strings.NewReader(s), len(s), false)
 
@@ -1346,6 +1470,10 @@ func (r *Request) WithJSON(object interface{}) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithJSON()") {
+		return r
+	}
+
 	b, err := json.Marshal(object)
 
 	if err != nil {
@@ -1396,6 +1524,10 @@ func (r *Request) WithForm(object interface{}) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithForm()") {
+		return r
+	}
+
 	f, err := form.EncodeToValues(object)
 
 	if err != nil {
@@ -1465,6 +1597,10 @@ func (r *Request) WithFormField(key string, value interface{}) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithFormField()") {
+		return r
+	}
+
 	if r.multipart != nil {
 		r.setType(opChain, "WithFormField()", "multipart/form-data", false)
 
@@ -1519,6 +1655,10 @@ func (r *Request) WithFile(key, path string, reader ...io.Reader) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithFile()") {
+		return r
+	}
+
 	if len(reader) > 1 {
 		opChain.fail(AssertionFailure{
 			Type: AssertUsage,
@@ -1553,6 +1693,10 @@ func (r *Request) WithFileBytes(key, path string, data []byte) *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithFileBytes()") {
+		return r
+	}
+
 	r.withFile(opChain, "WithFileBytes()", key, path, bytes.NewReader(data))
 
 	return r
@@ -1641,6 +1785,10 @@ func (r *Request) WithMultipart() *Request {
 		return r
 	}
 
+	if !r.checkOrder(opChain, "WithMultipart()") {
+		return r
+	}
+
 	r.setType(opChain, "WithMultipart()", "multipart/form-data", false)
 
 	if r.multipart == nil {
@@ -1658,6 +1806,9 @@ func (r *Request) WithMultipart() *Request {
 // Request is sent using Client interface, or WebsocketDialer in case of
 // WebSocket request.
 //
+// After calling Expect, there should not be any more calls of Expect or
+// other WithXXX methods on the same Request instance.
+//
 // Example:
 //
 //	req := NewRequestC(config, "PUT", "http://example.com/path")
@@ -1668,6 +1819,20 @@ func (r *Request) Expect() *Response {
 	opChain := r.chain.enter("Expect()")
 	defer opChain.leave()
 
+	if opChain.failed() {
+		return newResponse(responseOpts{
+			config: r.config,
+			chain:  opChain,
+		})
+	}
+
+	if !r.checkOrder(opChain, "Expect") {
+		return newResponse(responseOpts{
+			config: r.config,
+			chain:  opChain,
+		})
+	}
+
 	resp := r.roundTrip(opChain)
 
 	if resp == nil {
@@ -1681,6 +1846,8 @@ func (r *Request) Expect() *Response {
 		matcher(resp)
 	}
 
+	r.expectCalled = true
+
 	return resp
 }
 
@@ -2107,6 +2274,19 @@ func (r *Request) setBody(
 	r.bodySetter = setter
 }
 
+func (r *Request) checkOrder(opChain *chain, funcCall string) bool {
+	if r.expectCalled {
+		opChain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				fmt.Errorf("unexpected call to %s: Expect() has already been called", funcCall),
+			},
+		})
+		return false
+	}
+	return true
+}
+
 func concatPaths(a, b string) string {
 	if a == "" {
 		return b


Test patch:
diff --git a/request_test.go b/request_test.go
index c7ad3b1..7344244 100644
--- a/request_test.go
+++ b/request_test.go
@@ -16,6 +16,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/gorilla/websocket"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -2231,6 +2232,278 @@ func TestRequest_UsageChecks(t *testing.T) {
 		req.Expect()
 		req.chain.assertFailed(t)
 	})
+
+}
+
+func TestRequest_OrderChecks(t *testing.T) {
+	config := Config{
+		Reporter: newMockReporter(t),
+		Client:   &mockClient{},
+	}
+
+	t.Run("Expect after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.NotNil(t, req.Expect())
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithName after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithName("Test"))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithMatcher after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithMatcher(func(resp *Response) {
+			resp.Header("API-Version").NotEmpty()
+		}))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithTransformer after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithTransformer(func(r *http.Request) {
+			r.Header.Add("foo", "bar")
+		}))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithClient after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithClient(&mockClient{}))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithHandler after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithHandler(http.NotFoundHandler()))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithContext after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithContext(context.Background()))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithTimeout after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithTimeout(3*time.Second))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithRedirectPolicy after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithRedirectPolicy(FollowAllRedirects))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithMaxRedirects after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithMaxRedirects(3))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithRetryPolicy after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithRetryPolicy(DontRetry))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithMaxRetries after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithMaxRetries(10))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithRetryDelay after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithRetryDelay(time.Second, 5*time.Second))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithWebsocketUpgrade after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithWebsocketUpgrade())
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithWebsocketDialer after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithWebsocketDialer(&websocket.Dialer{}))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithPath after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/{repo}")
+		req.Expect()
+		assert.Same(t, req, req.WithPath("repo", "repo1"))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithPathObject after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/{repo}")
+		req.Expect()
+		assert.Same(t, req, req.WithPathObject(map[string]string{"repo": "repo1"}))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithQuery after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithQuery("a", 123))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithQueryObject after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithQueryObject(map[string]string{"a": "val"}))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithQueryString after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithQueryString("a=123&b=hello"))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithURL after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithURL("https://www.github.com"))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithHeaders after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithHeaders(
+			map[string]string{"Content-Type": "application/json"}))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithHeader after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithHeader("Content-Type", "application/json"))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithCookies after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithCookies(map[string]string{"key1": "val1"}))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithCookie after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithCookie("key1", "val1"))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithBasicAuth after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithBasicAuth("user", "pass"))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithHost after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithHost("localhost"))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithProto after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithProto("HTTP/1.1"))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithChunked after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithChunked(bytes.NewReader(nil)))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithBytes after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithBytes(nil))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithText after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithText("hello"))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithJSON after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithJSON(map[string]string{"key1": "val1"}))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithForm after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithForm(map[string]string{"key1": "val1"}))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithFormField after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithFormField("key1", 123))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithFile after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/").WithMultipart()
+		req.Expect()
+		assert.Same(t, req, req.WithFile("foo", "bar", strings.NewReader("baz")))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithFileBytes after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/").WithMultipart()
+		req.Expect()
+		assert.Same(t, req, req.WithFileBytes("foo", "bar", []byte("baz")))
+		req.chain.assertFailed(t)
+	})
+
+	t.Run("WithMultipart after an Expect", func(t *testing.T) {
+		req := NewRequestC(config, "GET", "/")
+		req.Expect()
+		assert.Same(t, req, req.WithMultipart())
+		req.chain.assertFailed(t)
+	})
 }
 
 func TestRequest_Panics(t *testing.T) {