Skip to content

Commit

Permalink
docs: add go doc
Browse files Browse the repository at this point in the history
  • Loading branch information
le-yams committed Nov 25, 2024
1 parent 227d2da commit 85a2710
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 6 deletions.
8 changes: 8 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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{},
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -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,
Expand Down
29 changes: 24 additions & 5 deletions invocations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -40,44 +36,59 @@ 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")
}
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 {
call.testState.Fatal(err)
}
}

// 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 {
Expand All @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions stubs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 {
Expand All @@ -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)
Expand Down
1 change: 0 additions & 1 deletion types.go

This file was deleted.

9 changes: 9 additions & 0 deletions verify.go
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
}

0 comments on commit 85a2710

Please sign in to comment.