From 85a2710875cb6390b6895e333dc75a8157981065 Mon Sep 17 00:00:00 2001 From: Yann D'Isanto Date: Mon, 25 Nov 2024 14:30:15 +0100 Subject: [PATCH] docs: add go doc --- api.go | 8 ++++++++ invocations.go | 29 ++++++++++++++++++++++++----- stubs.go | 7 +++++++ types.go | 1 - verify.go | 9 +++++++++ 5 files changed, 48 insertions(+), 6 deletions(-) delete mode 100644 types.go diff --git a/api.go b/api.go index 31f6d20..e86d4c3 100644 --- a/api.go +++ b/api.go @@ -16,6 +16,7 @@ type TestingT interface { Fatalf(format string, args ...any) } +// APIMock is a representation of a mocked API. It allows to stub HTTP calls and verify invocations. type APIMock struct { testServer *httptest.Server calls map[HTTPCall]http.HandlerFunc @@ -24,11 +25,13 @@ type APIMock struct { mu sync.Mutex } +// HTTPCall is a simple representation of an endpoint call. type HTTPCall struct { Method string Path string } +// API creates a new APIMock instance and starts a server exposing it. func API(testState TestingT) *APIMock { mockedAPI := &APIMock{ calls: map[HTTPCall]http.HandlerFunc{}, @@ -61,10 +64,12 @@ func API(testState TestingT) *APIMock { return mockedAPI } +// Close stops the underlying server. func (mockedAPI *APIMock) Close() { mockedAPI.testServer.Close() } +// GetURL returns the URL of the API underlying server. func (mockedAPI *APIMock) GetURL() *url.URL { testServerURL, err := url.Parse(mockedAPI.testServer.URL) if err != nil { @@ -73,10 +78,12 @@ func (mockedAPI *APIMock) GetURL() *url.URL { return testServerURL } +// GetHost returns the host of the API underlying server. func (mockedAPI *APIMock) GetHost() string { return mockedAPI.GetURL().Host } +// Stub creates a new StubBuilder instance for the given method and path. func (mockedAPI *APIMock) Stub(method string, path string) *StubBuilder { return &StubBuilder{ api: mockedAPI, @@ -87,6 +94,7 @@ func (mockedAPI *APIMock) Stub(method string, path string) *StubBuilder { } } +// Verify creates a new CallVerifier instance for the given method and path. func (mockedAPI *APIMock) Verify(method string, path string) *CallVerifier { return &CallVerifier{ api: mockedAPI, diff --git a/invocations.go b/invocations.go index 5ceb3de..11de3d7 100644 --- a/invocations.go +++ b/invocations.go @@ -10,17 +10,13 @@ import ( assertions "github.com/stretchr/testify/assert" ) +// Invocation represents a single HTTP request made to the mock server. type Invocation struct { request *http.Request payload []byte testState TestingT } -type InvocationRequestForm struct { - invocation *Invocation - formValues url.Values -} - func newInvocation(request *http.Request, testState TestingT) *Invocation { var data []byte var err error @@ -40,20 +36,30 @@ func newInvocation(request *http.Request, testState TestingT) *Invocation { } } +// GetRequest returns the invocation request func (call *Invocation) GetRequest() *http.Request { return call.request } +// GetPayload returns the invocation request payload func (call *Invocation) GetPayload() []byte { return call.payload } +// WithStatusCode asserts that the invocation response has the specified status code +func (call *Invocation) WithStatusCode(expected int) *Invocation { + assertions.Equal(call.testState, expected, call.request.Response.StatusCode) + return call +} + +// WithHeader asserts that the invocation request contains the specified header func (call *Invocation) WithHeader(name string, expectedValues ...string) *Invocation { values := call.request.Header.Values(name) assertions.Equal(call.testState, expectedValues, values) return call } +// WithoutHeader asserts that the invocation request does not contain the specified header func (call *Invocation) WithoutHeader(name string) *Invocation { if call.request.Header.Values(name) != nil { call.testState.Errorf("header '%s' found where it was expected not to") @@ -61,16 +67,19 @@ func (call *Invocation) WithoutHeader(name string) *Invocation { return call } +// WithPayload asserts that the invocation request contains the specified payload func (call *Invocation) WithPayload(expected []byte) *Invocation { assertions.Equal(call.testState, expected, call.GetPayload()) return call } +// WithStringPayload asserts that the invocation request contains the specified string payload func (call *Invocation) WithStringPayload(expected string) *Invocation { assertions.Equal(call.testState, expected, string(call.GetPayload())) return call } +// ReadJSONPayload reads the invocation request payload and unmarshals it into the specified object func (call *Invocation) ReadJSONPayload(obj any) { err := json.Unmarshal(call.GetPayload(), obj) if err != nil { @@ -78,6 +87,8 @@ func (call *Invocation) ReadJSONPayload(obj any) { } } +// WithUrlEncodedFormPayload assert that the invocation content type is application/x-www-form-urlencoded then returns +// the form to be asserted. func (call *Invocation) WithUrlEncodedFormPayload() *InvocationRequestForm { formValues, err := url.ParseQuery(string(call.GetPayload())) if err != nil { @@ -90,12 +101,20 @@ func (call *Invocation) WithUrlEncodedFormPayload() *InvocationRequestForm { } } +// InvocationRequestForm represents a form payload of an HTTP request made to the mock server. +type InvocationRequestForm struct { + invocation *Invocation + formValues url.Values +} + +// WithValues asserts that the form contains at least the specified values func (form InvocationRequestForm) WithValues(expectedValues map[string]string) { for key, value := range expectedValues { assertions.Equal(form.invocation.testState, value, form.formValues.Get(key)) } } +// WithValuesExactly asserts that the form contains exactly the specified values func (form InvocationRequestForm) WithValuesExactly(expectedValues map[string]string) { assertions.Equal(form.invocation.testState, len(expectedValues), len(form.formValues)) for key, value := range expectedValues { diff --git a/stubs.go b/stubs.go index f3c94e0..d10ba26 100644 --- a/stubs.go +++ b/stubs.go @@ -7,12 +7,14 @@ import ( "time" ) +// StubBuilder is a helper to build stubs for a specific HTTP call type StubBuilder struct { api *APIMock call *HTTPCall delay time.Duration } +// With creates a new stub for the HTTP call with the specified handler func (stub *StubBuilder) With(handler http.HandlerFunc) *APIMock { if stub.delay > 0 { stub.api.calls[*stub.call] = func(writer http.ResponseWriter, request *http.Request) { @@ -25,17 +27,21 @@ func (stub *StubBuilder) With(handler http.HandlerFunc) *APIMock { return stub.api } +// WithDelay adds a delay to the stub when called before it executes the corresponding handler func (stub *StubBuilder) WithDelay(delay time.Duration) *StubBuilder { stub.delay = delay return stub } +// WithStatusCode creates a new stub handler returning the specified status code func (stub *StubBuilder) WithStatusCode(statusCode int) *APIMock { return stub.With(func(writer http.ResponseWriter, request *http.Request) { writer.WriteHeader(statusCode) }) } +// WithJSON creates a new stub handler returning the specified status code and JSON content. +// The response header "Content-Type" is set to "application/json". func (stub *StubBuilder) WithJSON(statusCode int, content interface{}) *APIMock { body, err := json.Marshal(content) if err != nil { @@ -45,6 +51,7 @@ func (stub *StubBuilder) WithJSON(statusCode int, content interface{}) *APIMock return stub.WithBody(statusCode, body, "application/json") } +// WithBody creates a new stub handler returning the specified status code and body content. func (stub *StubBuilder) WithBody(statusCode int, body []byte, contentType string) *APIMock { return stub.With(func(writer http.ResponseWriter, request *http.Request) { writer.Header().Add("Content-Type", contentType) diff --git a/types.go b/types.go deleted file mode 100644 index 359f792..0000000 --- a/types.go +++ /dev/null @@ -1 +0,0 @@ -package mockhttp diff --git a/verify.go b/verify.go index 5e49c32..607641d 100644 --- a/verify.go +++ b/verify.go @@ -1,10 +1,13 @@ package mockhttp +// CallVerifier is a helper to verify invocations of a specific HTTP call type CallVerifier struct { api *APIMock call *HTTPCall } +// HasBeenCalled asserts that the HTTP call has been made the expected number of times. +// It returns all invocations of the call. func (verifier *CallVerifier) HasBeenCalled(expectedCallsCount int) []*Invocation { invocations := verifier.api.invocations[*verifier.call] actualCallsCount := len(invocations) @@ -14,6 +17,12 @@ func (verifier *CallVerifier) HasBeenCalled(expectedCallsCount int) []*Invocatio return invocations } +// HasBeenCalledOnce asserts that the HTTP call has been made exactly once then returns the invocation. func (verifier *CallVerifier) HasBeenCalledOnce() *Invocation { return verifier.HasBeenCalled(1)[0] } + +// HasNotBeenCalled asserts that no HTTP call has been made. +func (verifier *CallVerifier) HasNotBeenCalled() { + _ = verifier.HasBeenCalled(0) +}