From 18cd9a88182a212820b6d68557140bde7d9bfaa5 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Mon, 22 Jun 2026 13:50:00 +0000 Subject: [PATCH] Split HTTP assertion success cases --- hammy/README.md | 30 ++++++++-- hammy/httpassert/example_test.go | 28 ++++----- hammy/httpassert/httpassert.go | 40 +++++++++++++ hammy/httpassert/httpassert_test.go | 68 +++++++++++++++++++++- hammy/httpassert/recorder.go | 38 ++++++++++++ hammy/httpassert/recorder_test.go | 68 ++++++++++++++++++++-- hammy/httpassert/request.go | 38 ++++++++++++ hammy/httpassert/request_test.go | 90 ++++++++++++++++++++++++++++- 8 files changed, 372 insertions(+), 28 deletions(-) diff --git a/hammy/README.md b/hammy/README.md index 608d9d2..93ab3e6 100644 --- a/hammy/README.md +++ b/hammy/README.md @@ -43,14 +43,15 @@ Use `Match` when no typed wrapper fits or when the value is intentionally held a ## Dedicated Packages -Use `httpassert` for assertions on `*http.Response` values: +Use `httpassert` for assertions on `*http.Response` values. HTTP assertions use the constructor-style API: wrap the actual value once, then call assertion methods on the returned struct. This is also the preferred model for JSON/YAML constructor-style refactors because the actual value is stored by the wrapper. ```go import ha "github.com/gogunit/gunit/hammy/httpassert" -assert.Is(ha.Status(resp, http.StatusOK)) -assert.Is(ha.Header(resp, "Content-Type", "application/json")) -assert.Is(ha.BodyContains(resp, `"ok":true`)) +response := ha.Response(resp) +assert.Is(response.HasStatus(http.StatusOK)) +assert.Is(response.HeaderEqualTo("Content-Type", "application/json")) +assert.Is(response.HasBodyContaining(`"ok":true`)) ``` Use `jsonassert` for semantic JSON equality that ignores object key order and insignificant whitespace: @@ -122,13 +123,34 @@ func Test_payload_has_expected_type(t *testing.T) { ## HTTP (`hammy/httpassert`) +HTTP keeps constructor-style wrappers (`Response(resp)`, `Request(req)`, and `Recorder(rec)`) so actual HTTP values live in assertion structs while methods remain chainable and backward-compatible. + * [x] BodyContains * [x] BodyEqual +* [x] BodyEqualTo +* [x] BodyMatches * [x] BodyMatchesRegexp * [x] Header * [x] HeaderContains +* [x] HeaderEqualTo +* [x] HasBodyContaining +* [x] HasHeader +* [x] HasHeaderContaining +* [x] HasHost +* [x] HasMethod +* [x] HasPath +* [x] HasQueryParam +* [x] HasStatus +* [x] HasStatusInRange +* [x] Host +* [x] Method +* [x] Path +* [x] QueryParam * [x] Status * [x] StatusInRange +* [x] URL +* [x] URLEqual +* [x] URLEqualTo ## JSON (`hammy/jsonassert`) diff --git a/hammy/httpassert/example_test.go b/hammy/httpassert/example_test.go index 71e7bda..8f02fba 100644 --- a/hammy/httpassert/example_test.go +++ b/hammy/httpassert/example_test.go @@ -9,52 +9,52 @@ import ( "github.com/gogunit/gunit/hammy/httpassert" ) -func ExampleResp_Status() { - printExample(httpassert.Response(exampleResponse(http.StatusCreated, nil, "")).Status(http.StatusCreated)) +func ExampleResp_HasStatus() { + printExample(httpassert.Response(exampleResponse(http.StatusCreated, nil, "")).HasStatus(http.StatusCreated)) // Output: // message="got status <201>, wanted <201>" // success=true } -func ExampleResp_StatusInRange() { - printExample(httpassert.Response(exampleResponse(http.StatusNoContent, nil, "")).StatusInRange(200, 299)) +func ExampleResp_HasStatusInRange() { + printExample(httpassert.Response(exampleResponse(http.StatusNoContent, nil, "")).HasStatusInRange(200, 299)) // Output: // message="got status <204>, wanted in range <200..299>" // success=true } -func ExampleResp_Header() { +func ExampleResp_HeaderEqualTo() { resp := exampleResponse(http.StatusOK, http.Header{"Content-Type": {"application/json"}}, "") - printExample(httpassert.Response(resp).Header("Content-Type", "application/json")) + printExample(httpassert.Response(resp).HeaderEqualTo("Content-Type", "application/json")) // Output: // message="got header =, wanted " // success=true } -func ExampleResp_HeaderContains() { +func ExampleResp_HasHeaderContaining() { resp := exampleResponse(http.StatusOK, http.Header{"Content-Type": {"application/json; charset=utf-8"}}, "") - printExample(httpassert.Response(resp).HeaderContains("Content-Type", "application/json")) + printExample(httpassert.Response(resp).HasHeaderContaining("Content-Type", "application/json")) // Output: // message="got header =, wanted containing " // success=true } -func ExampleResp_BodyEqual() { - printExample(httpassert.Response(exampleResponse(http.StatusOK, nil, "hello world")).BodyEqual("hello world")) +func ExampleResp_BodyEqualTo() { + printExample(httpassert.Response(exampleResponse(http.StatusOK, nil, "hello world")).BodyEqualTo("hello world")) // Output: // message="got body , wanted equal to " // success=true } -func ExampleResp_BodyContains() { - printExample(httpassert.Response(exampleResponse(http.StatusOK, nil, "hello world")).BodyContains("world")) +func ExampleResp_HasBodyContaining() { + printExample(httpassert.Response(exampleResponse(http.StatusOK, nil, "hello world")).HasBodyContaining("world")) // Output: // message="got body , wanted containing " // success=true } -func ExampleResp_BodyMatchesRegexp() { - printExample(httpassert.Response(exampleResponse(http.StatusOK, nil, "status 204")).BodyMatchesRegexp(`status \d+`)) +func ExampleResp_BodyMatches() { + printExample(httpassert.Response(exampleResponse(http.StatusOK, nil, "status 204")).BodyMatches(`status \d+`)) // Output: // message="got body , wanted regexp " // success=true diff --git a/hammy/httpassert/httpassert.go b/hammy/httpassert/httpassert.go index bbccf2b..feb1212 100644 --- a/hammy/httpassert/httpassert.go +++ b/hammy/httpassert/httpassert.go @@ -18,6 +18,11 @@ type Resp struct { actual *http.Response } +// HasStatus asserts that the response status equals expected. It is an alias for Status. +func (r *Resp) HasStatus(expected int) hammy.AssertionMessage { + return r.Status(expected) +} + func (r *Resp) Status(expected int) hammy.AssertionMessage { resp := r.response() if resp == nil { @@ -26,6 +31,11 @@ func (r *Resp) Status(expected int) hammy.AssertionMessage { return hammy.Assert(resp.StatusCode == expected, "got status <%d>, wanted <%d>", resp.StatusCode, expected) } +// HasStatusInRange asserts that the response status is between min and max, inclusive. It is an alias for StatusInRange. +func (r *Resp) HasStatusInRange(min, max int) hammy.AssertionMessage { + return r.StatusInRange(min, max) +} + func (r *Resp) StatusInRange(min, max int) hammy.AssertionMessage { resp := r.response() if min > max { @@ -37,6 +47,16 @@ func (r *Resp) StatusInRange(min, max int) hammy.AssertionMessage { return hammy.Assert(resp.StatusCode >= min && resp.StatusCode <= max, "got status <%d>, wanted in range <%d..%d>", resp.StatusCode, min, max) } +// HeaderEqualTo asserts that the response header equals expected. It is an alias for Header. +func (r *Resp) HeaderEqualTo(key, expected string) hammy.AssertionMessage { + return r.Header(key, expected) +} + +// HasHeader asserts that the response header equals expected. It is an alias for Header. +func (r *Resp) HasHeader(key, expected string) hammy.AssertionMessage { + return r.Header(key, expected) +} + func (r *Resp) Header(key, expected string) hammy.AssertionMessage { resp := r.response() if resp == nil { @@ -46,6 +66,11 @@ func (r *Resp) Header(key, expected string) hammy.AssertionMessage { return hammy.Assert(actual == expected, "got header <%s>=<%s>, wanted <%s>", key, actual, expected) } +// HasHeaderContaining asserts that the response header contains expected. It is an alias for HeaderContains. +func (r *Resp) HasHeaderContaining(key, expected string) hammy.AssertionMessage { + return r.HeaderContains(key, expected) +} + func (r *Resp) HeaderContains(key, expected string) hammy.AssertionMessage { resp := r.response() if resp == nil { @@ -55,6 +80,11 @@ func (r *Resp) HeaderContains(key, expected string) hammy.AssertionMessage { return hammy.Assert(strings.Contains(actual, expected), "got header <%s>=<%s>, wanted containing <%s>", key, actual, expected) } +// BodyEqualTo asserts that the response body equals expected. It is an alias for BodyEqual. +func (r *Resp) BodyEqualTo(expected string) hammy.AssertionMessage { + return r.BodyEqual(expected) +} + func (r *Resp) BodyEqual(expected string) hammy.AssertionMessage { actual, result := readBody(r.response()) if !result.IsSuccessful { @@ -63,6 +93,11 @@ func (r *Resp) BodyEqual(expected string) hammy.AssertionMessage { return hammy.Assert(actual == expected, "got body <%s>, wanted equal to <%s>", actual, expected) } +// HasBodyContaining asserts that the response body contains expected. It is an alias for BodyContains. +func (r *Resp) HasBodyContaining(expected string) hammy.AssertionMessage { + return r.BodyContains(expected) +} + func (r *Resp) BodyContains(expected string) hammy.AssertionMessage { actual, result := readBody(r.response()) if !result.IsSuccessful { @@ -71,6 +106,11 @@ func (r *Resp) BodyContains(expected string) hammy.AssertionMessage { return hammy.Assert(strings.Contains(actual, expected), "got body <%s>, wanted containing <%s>", actual, expected) } +// BodyMatches asserts that the response body matches pattern. It is an alias for BodyMatchesRegexp. +func (r *Resp) BodyMatches(pattern string) hammy.AssertionMessage { + return r.BodyMatchesRegexp(pattern) +} + func (r *Resp) BodyMatchesRegexp(pattern string) hammy.AssertionMessage { actual, result := readBody(r.response()) if !result.IsSuccessful { diff --git a/hammy/httpassert/httpassert_test.go b/hammy/httpassert/httpassert_test.go index 02a6e01..c1a61a4 100644 --- a/hammy/httpassert/httpassert_test.go +++ b/hammy/httpassert/httpassert_test.go @@ -157,13 +157,19 @@ func Test_BodyMatchesRegexp_failure_invalid_pattern(t *testing.T) { aSpy.HadErrorContaining(t, "invalid regexp <(>") } -func Test_Body_assertions_restore_body(t *testing.T) { +func Test_Body_assertion_restores_body(t *testing.T) { assert := hammy.New(t) resp := newResponse(http.StatusOK, nil, "hello world") assert.Is(httpassert.Response(resp).BodyContains("hello")) - assert.Is(httpassert.Response(resp).BodyEqual("hello world")) - assert.Is(httpassert.Response(resp).BodyMatchesRegexp(`world$`)) + + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("read restored body: %v", err) + } + if string(body) != "hello world" { + t.Fatalf("got restored body %q, wanted %q", string(body), "hello world") + } } func Test_Body_assertion_failure_nil_response(t *testing.T) { @@ -218,3 +224,59 @@ func Test_Response_methods_failure_nil_receiver(t *testing.T) { assert.Is(result) aSpy.HadErrorContaining(t, "got nil response") } + +func Test_Response_HasStatus_alias_success(t *testing.T) { + assert := hammy.New(t) + resp := newResponse(http.StatusOK, nil, "") + + assert.Is(httpassert.Response(resp).HasStatus(http.StatusOK)) +} + +func Test_Response_HasStatusInRange_alias_success(t *testing.T) { + assert := hammy.New(t) + resp := newResponse(http.StatusOK, nil, "") + + assert.Is(httpassert.Response(resp).HasStatusInRange(200, 299)) +} + +func Test_Response_HeaderEqualTo_alias_success(t *testing.T) { + assert := hammy.New(t) + resp := newResponse(http.StatusOK, http.Header{"Content-Type": {"application/json; charset=utf-8"}}, "") + + assert.Is(httpassert.Response(resp).HeaderEqualTo("Content-Type", "application/json; charset=utf-8")) +} + +func Test_Response_HasHeader_alias_success(t *testing.T) { + assert := hammy.New(t) + resp := newResponse(http.StatusOK, http.Header{"Content-Type": {"application/json; charset=utf-8"}}, "") + + assert.Is(httpassert.Response(resp).HasHeader("Content-Type", "application/json; charset=utf-8")) +} + +func Test_Response_HasHeaderContaining_alias_success(t *testing.T) { + assert := hammy.New(t) + resp := newResponse(http.StatusOK, http.Header{"Content-Type": {"application/json; charset=utf-8"}}, "") + + assert.Is(httpassert.Response(resp).HasHeaderContaining("Content-Type", "application/json")) +} + +func Test_Response_BodyEqualTo_alias_success(t *testing.T) { + assert := hammy.New(t) + resp := newResponse(http.StatusOK, nil, "status 204") + + assert.Is(httpassert.Response(resp).BodyEqualTo("status 204")) +} + +func Test_Response_HasBodyContaining_alias_success(t *testing.T) { + assert := hammy.New(t) + resp := newResponse(http.StatusOK, nil, "status 204") + + assert.Is(httpassert.Response(resp).HasBodyContaining("204")) +} + +func Test_Response_BodyMatches_alias_success(t *testing.T) { + assert := hammy.New(t) + resp := newResponse(http.StatusOK, nil, "status 204") + + assert.Is(httpassert.Response(resp).BodyMatches(`status \d+`)) +} diff --git a/hammy/httpassert/recorder.go b/hammy/httpassert/recorder.go index 7309938..7089956 100644 --- a/hammy/httpassert/recorder.go +++ b/hammy/httpassert/recorder.go @@ -19,6 +19,9 @@ type RecorderAssert struct { actual *httptest.ResponseRecorder } +// HasStatus asserts that the recorded response status equals expected. It is an alias for Status. +func (r *RecorderAssert) HasStatus(expected int) hammy.AssertionMessage { return r.Status(expected) } + // Status asserts that the recorded response status equals expected. func (r *RecorderAssert) Status(expected int) hammy.AssertionMessage { resp, result := r.response() @@ -28,6 +31,11 @@ func (r *RecorderAssert) Status(expected int) hammy.AssertionMessage { return hammy.Assert(resp.StatusCode == expected, "got status <%d>, wanted <%d>", resp.StatusCode, expected) } +// HasStatusInRange asserts that the recorded response status is between min and max, inclusive. It is an alias for StatusInRange. +func (r *RecorderAssert) HasStatusInRange(min, max int) hammy.AssertionMessage { + return r.StatusInRange(min, max) +} + // StatusInRange asserts that the recorded response status is between min and max, inclusive. func (r *RecorderAssert) StatusInRange(min, max int) hammy.AssertionMessage { if min > max { @@ -40,6 +48,16 @@ func (r *RecorderAssert) StatusInRange(min, max int) hammy.AssertionMessage { return hammy.Assert(resp.StatusCode >= min && resp.StatusCode <= max, "got status <%d>, wanted in range <%d..%d>", resp.StatusCode, min, max) } +// HeaderEqualTo asserts that the recorded response header equals expected. It is an alias for Header. +func (r *RecorderAssert) HeaderEqualTo(key, expected string) hammy.AssertionMessage { + return r.Header(key, expected) +} + +// HasHeader asserts that the recorded response header equals expected. It is an alias for Header. +func (r *RecorderAssert) HasHeader(key, expected string) hammy.AssertionMessage { + return r.Header(key, expected) +} + // Header asserts that the recorded response header equals expected. func (r *RecorderAssert) Header(key, expected string) hammy.AssertionMessage { resp, result := r.response() @@ -50,6 +68,11 @@ func (r *RecorderAssert) Header(key, expected string) hammy.AssertionMessage { return hammy.Assert(actual == expected, "got header <%s>=<%s>, wanted <%s>", key, actual, expected) } +// HasHeaderContaining asserts that the recorded response header contains expected. It is an alias for HeaderContains. +func (r *RecorderAssert) HasHeaderContaining(key, expected string) hammy.AssertionMessage { + return r.HeaderContains(key, expected) +} + // HeaderContains asserts that the recorded response header contains expected. func (r *RecorderAssert) HeaderContains(key, expected string) hammy.AssertionMessage { resp, result := r.response() @@ -60,6 +83,11 @@ func (r *RecorderAssert) HeaderContains(key, expected string) hammy.AssertionMes return hammy.Assert(strings.Contains(actual, expected), "got header <%s>=<%s>, wanted containing <%s>", key, actual, expected) } +// BodyEqualTo asserts that the recorded response body equals expected. It is an alias for BodyEqual. +func (r *RecorderAssert) BodyEqualTo(expected string) hammy.AssertionMessage { + return r.BodyEqual(expected) +} + // BodyEqual asserts that the recorded response body equals expected. func (r *RecorderAssert) BodyEqual(expected string) hammy.AssertionMessage { actual, result := r.body() @@ -69,6 +97,11 @@ func (r *RecorderAssert) BodyEqual(expected string) hammy.AssertionMessage { return hammy.Assert(actual == expected, "got body <%s>, wanted equal to <%s>", actual, expected) } +// HasBodyContaining asserts that the recorded response body contains expected. It is an alias for BodyContains. +func (r *RecorderAssert) HasBodyContaining(expected string) hammy.AssertionMessage { + return r.BodyContains(expected) +} + // BodyContains asserts that the recorded response body contains expected. func (r *RecorderAssert) BodyContains(expected string) hammy.AssertionMessage { actual, result := r.body() @@ -78,6 +111,11 @@ func (r *RecorderAssert) BodyContains(expected string) hammy.AssertionMessage { return hammy.Assert(strings.Contains(actual, expected), "got body <%s>, wanted containing <%s>", actual, expected) } +// BodyMatches asserts that the recorded response body matches pattern. It is an alias for BodyMatchesRegexp. +func (r *RecorderAssert) BodyMatches(pattern string) hammy.AssertionMessage { + return r.BodyMatchesRegexp(pattern) +} + // BodyMatchesRegexp asserts that the recorded response body matches pattern. func (r *RecorderAssert) BodyMatchesRegexp(pattern string) hammy.AssertionMessage { actual, result := r.body() diff --git a/hammy/httpassert/recorder_test.go b/hammy/httpassert/recorder_test.go index 78e71f2..07808cc 100644 --- a/hammy/httpassert/recorder_test.go +++ b/hammy/httpassert/recorder_test.go @@ -148,13 +148,15 @@ func Test_Recorder_BodyMatchesRegexp_failure_invalid_pattern(t *testing.T) { aSpy.HadErrorContaining(t, "invalid regexp <(>") } -func Test_Recorder_Body_assertions_restore_body(t *testing.T) { +func Test_Recorder_Body_assertion_keeps_body(t *testing.T) { assert := hammy.New(t) - recorderAssert := httpassert.Recorder(newRecorder(http.StatusOK, nil, "hello world")) + rec := newRecorder(http.StatusOK, nil, "hello world") + + assert.Is(httpassert.Recorder(rec).BodyContains("hello")) - assert.Is(recorderAssert.BodyContains("hello")) - assert.Is(recorderAssert.BodyEqual("hello world")) - assert.Is(recorderAssert.BodyMatchesRegexp(`world$`)) + if rec.Body.String() != "hello world" { + t.Fatalf("got recorder body %q, wanted %q", rec.Body.String(), "hello world") + } } func newRecorder(status int, headers http.Header, body string) *httptest.ResponseRecorder { @@ -168,3 +170,59 @@ func newRecorder(status int, headers http.Header, body string) *httptest.Respons _, _ = recorder.WriteString(body) return recorder } + +func Test_Recorder_HasStatus_alias_success(t *testing.T) { + assert := hammy.New(t) + rec := newRecorder(http.StatusOK, nil, "") + + assert.Is(httpassert.Recorder(rec).HasStatus(http.StatusOK)) +} + +func Test_Recorder_HasStatusInRange_alias_success(t *testing.T) { + assert := hammy.New(t) + rec := newRecorder(http.StatusOK, nil, "") + + assert.Is(httpassert.Recorder(rec).HasStatusInRange(200, 299)) +} + +func Test_Recorder_HeaderEqualTo_alias_success(t *testing.T) { + assert := hammy.New(t) + rec := newRecorder(http.StatusOK, http.Header{"Content-Type": {"application/json; charset=utf-8"}}, "") + + assert.Is(httpassert.Recorder(rec).HeaderEqualTo("Content-Type", "application/json; charset=utf-8")) +} + +func Test_Recorder_HasHeader_alias_success(t *testing.T) { + assert := hammy.New(t) + rec := newRecorder(http.StatusOK, http.Header{"Content-Type": {"application/json; charset=utf-8"}}, "") + + assert.Is(httpassert.Recorder(rec).HasHeader("Content-Type", "application/json; charset=utf-8")) +} + +func Test_Recorder_HasHeaderContaining_alias_success(t *testing.T) { + assert := hammy.New(t) + rec := newRecorder(http.StatusOK, http.Header{"Content-Type": {"application/json; charset=utf-8"}}, "") + + assert.Is(httpassert.Recorder(rec).HasHeaderContaining("Content-Type", "application/json")) +} + +func Test_Recorder_BodyEqualTo_alias_success(t *testing.T) { + assert := hammy.New(t) + rec := newRecorder(http.StatusOK, nil, "status 204") + + assert.Is(httpassert.Recorder(rec).BodyEqualTo("status 204")) +} + +func Test_Recorder_HasBodyContaining_alias_success(t *testing.T) { + assert := hammy.New(t) + rec := newRecorder(http.StatusOK, nil, "status 204") + + assert.Is(httpassert.Recorder(rec).HasBodyContaining("204")) +} + +func Test_Recorder_BodyMatches_alias_success(t *testing.T) { + assert := hammy.New(t) + rec := newRecorder(http.StatusOK, nil, "status 204") + + assert.Is(httpassert.Recorder(rec).BodyMatches(`status \d+`)) +} diff --git a/hammy/httpassert/request.go b/hammy/httpassert/request.go index fb9560f..6930795 100644 --- a/hammy/httpassert/request.go +++ b/hammy/httpassert/request.go @@ -17,6 +17,9 @@ type Req struct { actual *http.Request } +// HasMethod asserts that the request method equals expected. It is an alias for Method. +func (r *Req) HasMethod(expected string) hammy.AssertionMessage { return r.Method(expected) } + func (r *Req) Method(expected string) hammy.AssertionMessage { if r == nil || r.actual == nil { return hammy.Assert(false, "got nil request, wanted method <%s>", expected) @@ -24,6 +27,9 @@ func (r *Req) Method(expected string) hammy.AssertionMessage { return hammy.Assert(r.actual.Method == expected, "got method <%s>, wanted <%s>", r.actual.Method, expected) } +// HasPath asserts that the request path equals expected. It is an alias for Path. +func (r *Req) HasPath(expected string) hammy.AssertionMessage { return r.Path(expected) } + func (r *Req) Path(expected string) hammy.AssertionMessage { if r == nil || r.actual == nil { return hammy.Assert(false, "got nil request, wanted path <%s>", expected) @@ -34,6 +40,9 @@ func (r *Req) Path(expected string) hammy.AssertionMessage { return hammy.Assert(r.actual.URL.Path == expected, "got path <%s>, wanted <%s>", r.actual.URL.Path, expected) } +// URLEqualTo asserts that the request URL equals expected. It is an alias for URL. +func (r *Req) URLEqualTo(expected string) hammy.AssertionMessage { return r.URL(expected) } + func (r *Req) URL(expected string) hammy.AssertionMessage { if r == nil || r.actual == nil { return hammy.Assert(false, "got nil request, wanted URL <%s>", expected) @@ -49,6 +58,9 @@ func (r *Req) URLEqual(expected string) hammy.AssertionMessage { return r.URL(expected) } +// HasHost asserts that the request host equals expected. It is an alias for Host. +func (r *Req) HasHost(expected string) hammy.AssertionMessage { return r.Host(expected) } + func (r *Req) Host(expected string) hammy.AssertionMessage { if r == nil || r.actual == nil { return hammy.Assert(false, "got nil request, wanted host <%s>", expected) @@ -56,6 +68,14 @@ func (r *Req) Host(expected string) hammy.AssertionMessage { return hammy.Assert(r.actual.Host == expected, "got host <%s>, wanted <%s>", r.actual.Host, expected) } +// HeaderEqualTo asserts that the request header equals expected. It is an alias for Header. +func (r *Req) HeaderEqualTo(key, expected string) hammy.AssertionMessage { + return r.Header(key, expected) +} + +// HasHeader asserts that the request header equals expected. It is an alias for Header. +func (r *Req) HasHeader(key, expected string) hammy.AssertionMessage { return r.Header(key, expected) } + func (r *Req) Header(key, expected string) hammy.AssertionMessage { if r == nil || r.actual == nil { return hammy.Assert(false, "got nil request, wanted header <%s> equal to <%s>", key, expected) @@ -64,6 +84,11 @@ func (r *Req) Header(key, expected string) hammy.AssertionMessage { return hammy.Assert(actual == expected, "got header <%s>=<%s>, wanted <%s>", key, actual, expected) } +// HasHeaderContaining asserts that the request header contains expected. It is an alias for HeaderContains. +func (r *Req) HasHeaderContaining(key, expected string) hammy.AssertionMessage { + return r.HeaderContains(key, expected) +} + func (r *Req) HeaderContains(key, expected string) hammy.AssertionMessage { if r == nil || r.actual == nil { return hammy.Assert(false, "got nil request, wanted header <%s> containing <%s>", key, expected) @@ -72,6 +97,11 @@ func (r *Req) HeaderContains(key, expected string) hammy.AssertionMessage { return hammy.Assert(strings.Contains(actual, expected), "got header <%s>=<%s>, wanted containing <%s>", key, actual, expected) } +// HasQueryParam asserts that the request query parameter equals expected. It is an alias for QueryParam. +func (r *Req) HasQueryParam(key, expected string) hammy.AssertionMessage { + return r.QueryParam(key, expected) +} + func (r *Req) QueryParam(key, expected string) hammy.AssertionMessage { if r == nil || r.actual == nil { return hammy.Assert(false, "got nil request, wanted query param <%s> equal to <%s>", key, expected) @@ -83,6 +113,9 @@ func (r *Req) QueryParam(key, expected string) hammy.AssertionMessage { return hammy.Assert(actual == expected, "got query param <%s>=<%s>, wanted <%s>", key, actual, expected) } +// BodyEqualTo asserts that the request body equals expected. It is an alias for BodyEqual. +func (r *Req) BodyEqualTo(expected string) hammy.AssertionMessage { return r.BodyEqual(expected) } + func (r *Req) BodyEqual(expected string) hammy.AssertionMessage { actual, result := readRequestBody(r) if !result.IsSuccessful { @@ -91,6 +124,11 @@ func (r *Req) BodyEqual(expected string) hammy.AssertionMessage { return hammy.Assert(actual == expected, "got body <%s>, wanted equal to <%s>", actual, expected) } +// HasBodyContaining asserts that the request body contains expected. It is an alias for BodyContains. +func (r *Req) HasBodyContaining(expected string) hammy.AssertionMessage { + return r.BodyContains(expected) +} + func (r *Req) BodyContains(expected string) hammy.AssertionMessage { actual, result := readRequestBody(r) if !result.IsSuccessful { diff --git a/hammy/httpassert/request_test.go b/hammy/httpassert/request_test.go index 37aeba0..f945bbe 100644 --- a/hammy/httpassert/request_test.go +++ b/hammy/httpassert/request_test.go @@ -61,6 +61,12 @@ func Test_Request_URL_success(t *testing.T) { req := newRequest(http.MethodGet, "https://example.com/widgets/1?expand=true", "") assert.Is(httpassert.Request(req).URL("https://example.com/widgets/1?expand=true")) +} + +func Test_Request_URLEqual_success(t *testing.T) { + assert := hammy.New(t) + req := newRequest(http.MethodGet, "https://example.com/widgets/1?expand=true", "") + assert.Is(httpassert.Request(req).URLEqual("https://example.com/widgets/1?expand=true")) } @@ -189,12 +195,19 @@ func Test_Request_BodyContains_failure(t *testing.T) { aSpy.HadErrorContaining(t, "got body , wanted containing ") } -func Test_Request_Body_assertions_restore_body(t *testing.T) { +func Test_Request_Body_assertion_restores_body(t *testing.T) { assert := hammy.New(t) req := newRequest(http.MethodPost, "https://example.com/widgets", "hello world") assert.Is(httpassert.Request(req).BodyContains("hello")) - assert.Is(httpassert.Request(req).BodyEqual("hello world")) + + body, err := io.ReadAll(req.Body) + if err != nil { + t.Fatalf("read restored body: %v", err) + } + if string(body) != "hello world" { + t.Fatalf("got restored body %q, wanted %q", string(body), "hello world") + } } func Test_Request_Body_assertion_failure_nil_request(t *testing.T) { @@ -245,3 +258,76 @@ func (requestErrorReadCloser) Read([]byte) (int, error) { func (requestErrorReadCloser) Close() error { return nil } + +func Test_Request_HasMethod_alias_success(t *testing.T) { + assert := hammy.New(t) + req := newRequest(http.MethodPost, "https://example.com/widgets/1?expand=true", "") + + assert.Is(httpassert.Request(req).HasMethod(http.MethodPost)) +} + +func Test_Request_HasPath_alias_success(t *testing.T) { + assert := hammy.New(t) + req := newRequest(http.MethodPost, "https://example.com/widgets/1?expand=true", "") + + assert.Is(httpassert.Request(req).HasPath("/widgets/1")) +} + +func Test_Request_URLEqualTo_alias_success(t *testing.T) { + assert := hammy.New(t) + req := newRequest(http.MethodPost, "https://example.com/widgets/1?expand=true", "") + + assert.Is(httpassert.Request(req).URLEqualTo("https://example.com/widgets/1?expand=true")) +} + +func Test_Request_HasHost_alias_success(t *testing.T) { + assert := hammy.New(t) + req := newRequest(http.MethodPost, "https://example.com/widgets/1?expand=true", "") + + assert.Is(httpassert.Request(req).HasHost("example.com")) +} + +func Test_Request_HeaderEqualTo_alias_success(t *testing.T) { + assert := hammy.New(t) + req := newRequest(http.MethodPost, "https://example.com/widgets/1?expand=true", "") + req.Header.Set("Accept", "application/json; charset=utf-8") + + assert.Is(httpassert.Request(req).HeaderEqualTo("Accept", "application/json; charset=utf-8")) +} + +func Test_Request_HasHeader_alias_success(t *testing.T) { + assert := hammy.New(t) + req := newRequest(http.MethodPost, "https://example.com/widgets/1?expand=true", "") + req.Header.Set("Accept", "application/json; charset=utf-8") + + assert.Is(httpassert.Request(req).HasHeader("Accept", "application/json; charset=utf-8")) +} + +func Test_Request_HasHeaderContaining_alias_success(t *testing.T) { + assert := hammy.New(t) + req := newRequest(http.MethodPost, "https://example.com/widgets/1?expand=true", "") + req.Header.Set("Accept", "application/json; charset=utf-8") + + assert.Is(httpassert.Request(req).HasHeaderContaining("Accept", "application/json")) +} + +func Test_Request_HasQueryParam_alias_success(t *testing.T) { + assert := hammy.New(t) + req := newRequest(http.MethodPost, "https://example.com/widgets/1?expand=true", "") + + assert.Is(httpassert.Request(req).HasQueryParam("expand", "true")) +} + +func Test_Request_BodyEqualTo_alias_success(t *testing.T) { + assert := hammy.New(t) + req := newRequest(http.MethodPost, "https://example.com/widgets/1?expand=true", "hello world") + + assert.Is(httpassert.Request(req).BodyEqualTo("hello world")) +} + +func Test_Request_HasBodyContaining_alias_success(t *testing.T) { + assert := hammy.New(t) + req := newRequest(http.MethodPost, "https://example.com/widgets/1?expand=true", "hello world") + + assert.Is(httpassert.Request(req).HasBodyContaining("world")) +}