Skip to content

Commit

Permalink
Merge pull request #41 from steinfletcher/feature/standalone-mocks
Browse files Browse the repository at this point in the history
support mocks in standalone mode so mocks can be used outside of api …
  • Loading branch information
steinfletcher authored May 11, 2019
2 parents 54d33ef + e475d45 commit 5bf3a13
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 28 deletions.
32 changes: 14 additions & 18 deletions apitest.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ func (r *Response) End() Result {
res := r.apiTest.report()
return Result{Response: res}
}
res := r.execute()
res := r.runTest()
return Result{Response: res}
}

Expand Down Expand Up @@ -467,7 +467,7 @@ func (a *APITest) report() *http.Response {
}

execTime := time.Now().UTC()
res := a.response.execute()
res := a.response.runTest()
finishTime := time.Now().UTC()

a.recorder.
Expand Down Expand Up @@ -547,24 +547,20 @@ func createHash(meta map[string]interface{}) string {
return fmt.Sprintf("%d_%d", prefix.Sum32(), suffix.Sum32())
}

func (r *Response) execute() *http.Response {
apiTest := r.apiTest
if len(apiTest.mocks) > 0 {
apiTest.transport = newTransport(
apiTest.mocks,
apiTest.httpClient,
apiTest.debugEnabled,
apiTest.mocksObserver,
func (r *Response) runTest() *http.Response {
a := r.apiTest
if len(a.mocks) > 0 {
a.transport = newTransport(
a.mocks,
a.httpClient,
a.debugEnabled,
a.mocksObserver,
r.apiTest,
)
defer apiTest.transport.Reset()
apiTest.transport.Hijack()
defer a.transport.Reset()
a.transport.Hijack()
}
return apiTest.run()
}

func (a *APITest) run() *http.Response {
res, req := a.runTest()
res, req := a.doRequest()

defer func() {
if len(a.observers) > 0 {
Expand Down Expand Up @@ -598,7 +594,7 @@ func (a *APITest) assertFunc(res *httptest.ResponseRecorder, req *http.Request)
return nil
}

func (a *APITest) runTest() (*httptest.ResponseRecorder, *http.Request) {
func (a *APITest) doRequest() (*httptest.ResponseRecorder, *http.Request) {
req := a.BuildRequest()
if a.request.interceptor != nil {
a.request.interceptor(req)
Expand Down
12 changes: 6 additions & 6 deletions apitest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (
func TestApiTest_AddsJSONBodyToRequest(t *testing.T) {
handler := http.NewServeMux()
handler.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
bytes, _ := ioutil.ReadAll(r.Body)
if string(bytes) != `{"a": 12345}` {
data, _ := ioutil.ReadAll(r.Body)
if string(data) != `{"a": 12345}` {
w.WriteHeader(http.StatusInternalServerError)
return
}
Expand All @@ -42,8 +42,8 @@ func TestApiTest_AddsJSONBodyToRequest(t *testing.T) {
func TestApiTest_AddsJSONBodyToRequestUsingJSON(t *testing.T) {
handler := http.NewServeMux()
handler.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
bytes, _ := ioutil.ReadAll(r.Body)
if string(bytes) != `{"a": 12345}` {
data, _ := ioutil.ReadAll(r.Body)
if string(data) != `{"a": 12345}` {
w.WriteHeader(http.StatusInternalServerError)
return
}
Expand All @@ -66,8 +66,8 @@ func TestApiTest_AddsJSONBodyToRequestUsingJSON(t *testing.T) {
func TestApiTest_AddsTextBodyToRequest(t *testing.T) {
handler := http.NewServeMux()
handler.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
bytes, _ := ioutil.ReadAll(r.Body)
if string(bytes) != `hello` {
data, _ := ioutil.ReadAll(r.Body)
if string(data) != `hello` {
w.WriteHeader(http.StatusInternalServerError)
return
}
Expand Down
25 changes: 25 additions & 0 deletions docs/content/docs/mocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,29 @@ apitest.New().

Note that multiple mocks can be defined. Due to FIFO ordering if a request matches more than one mock the first mock matched is used.

## Standalone Mode

You can use mocks outside of API tests by using the `EndStandalone` termination method on the mock builder. This is useful for testing http clients outside of api tests.

```go
func TestMocks_Standalone(t *testing.T) {
cli := http.Client{Timeout: 5}
defer NewMock().
Post("http://localhost:8080/path").
Body(`{"a", 12345}`).
RespondWith().
Status(http.StatusCreated).
EndStandalone()()

resp, err := cli.Post("http://localhost:8080/path",
"application/json",
strings.NewReader(`{"a", 12345}`))

assert.NoError(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
}
```

`EndStandalone` returns a function that should be invoked after the test runs to reset the http transport to the default configuration.

<!-- TODO: explain the matchers -->
40 changes: 36 additions & 4 deletions mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,11 @@ func buildResponseFromMock(mockResponse *MockResponse) *http.Response {
}

type Mock struct {
isUsed bool
request *MockRequest
response *MockResponse
isUsed bool
request *MockRequest
response *MockResponse
httpClient *http.Client
debugStandalone bool
}

type MockRequest struct {
Expand Down Expand Up @@ -234,6 +236,19 @@ func NewMock() *Mock {
return mock
}

// Debug is used to set debug mode for mocks in standalone mode.
// This is overridden by the debug setting in the `APITest` struct
func (m *Mock) Debug() *Mock {
m.debugStandalone = true
return m
}

// HttpClient allows the developer to provide a custom http client when using mocks
func (m *Mock) HttpClient(cli *http.Client) *Mock {
m.httpClient = cli
return m
}

func (m *Mock) Get(u string) *MockRequest {
m.parseUrl(u)
m.request.method = http.MethodGet
Expand Down Expand Up @@ -405,6 +420,19 @@ func (r *MockResponse) End() *Mock {
return r.mock
}

func (r *MockResponse) EndStandalone() func() {
transport := newTransport(
[]*Mock{r.mock},
r.mock.httpClient,
r.mock.debugStandalone,
nil,
nil,
)
resetFunc := func() { transport.Reset() }
transport.Hijack()
return resetFunc
}

// Matcher type accepts the actual request and a mock request to match against.
// Will return an error that describes why there was a mismatch if the inputs do not match or nil if they do.
type Matcher func(*http.Request, *MockRequest) error
Expand Down Expand Up @@ -568,9 +596,13 @@ var bodyMatcher = func(req *http.Request, spec *MockRequest) error {
return nil
}

if req.Body == nil {
return errors.New("expected a body but received none")
}

body, err := ioutil.ReadAll(req.Body)
if err != nil {
panic(err)
return err
}
if len(body) == 0 {
return errors.New("expected a body but received none")
Expand Down
35 changes: 35 additions & 0 deletions mocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,41 @@ func TestMocks_Response_Cookies(t *testing.T) {
}, response.Cookies())
}

func TestMocks_Standalone(t *testing.T) {
cli := http.Client{Timeout: 5}
defer NewMock().
Post("http://localhost:8080/path").
Body(`{"a", 12345}`).
RespondWith().
Status(http.StatusCreated).
EndStandalone()()

resp, err := cli.Post("http://localhost:8080/path",
"application/json",
strings.NewReader(`{"a", 12345}`))

assert.NoError(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
}

func TestMocks_Standalone_WithCustomerHTTPClient(t *testing.T) {
httpClient := customCli
defer NewMock().
HttpClient(httpClient).
Post("http://localhost:8080/path").
Body(`{"a", 12345}`).
RespondWith().
Status(http.StatusCreated).
EndStandalone()()

resp, err := httpClient.Post("http://localhost:8080/path",
"application/json",
strings.NewReader(`{"a", 12345}`))

assert.NoError(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
}

func TestMocks_ApiTest_WithMocks(t *testing.T) {
tests := []struct {
name string
Expand Down

0 comments on commit 5bf3a13

Please sign in to comment.