Skip to content

Commit

Permalink
Adds custom matching functions to mocks (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
webbgeorge authored Mar 13, 2019
1 parent 7d10450 commit 548b788
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 5 deletions.
8 changes: 8 additions & 0 deletions examples/mocks/api_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"errors"
"github.com/steinfletcher/apitest"
"net/http"
"testing"
Expand All @@ -20,6 +21,13 @@ func TestGetUser_Success(t *testing.T) {

var getPreferencesMock = apitest.NewMock().
Get("/preferences/12345").
AddMatcher(func(r *http.Request, mr *apitest.MockRequest) error {
// Custom matching func for URL Scheme
if r.URL.Scheme != "http" {
return errors.New("request did not have 'http' scheme")
}
return nil
}).
RespondWith().
Body(`{"is_contactable": true}`).
Status(http.StatusOK).
Expand Down
17 changes: 12 additions & 5 deletions mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ type MockRequest struct {
query map[string][]string
queryPresent []string
body string
matchers []Matcher
}

type MockResponse struct {
Expand All @@ -215,9 +216,10 @@ type MockResponse struct {
func NewMock() *Mock {
mock := &Mock{}
req := &MockRequest{
mock: mock,
headers: map[string][]string{},
query: map[string][]string{},
mock: mock,
headers: map[string][]string{},
query: map[string][]string{},
matchers: defaultMatchers,
}
res := &MockResponse{
mock: mock,
Expand Down Expand Up @@ -280,7 +282,7 @@ func matches(req *http.Request, mocks []*Mock) (*MockResponse, error) {
}

var mockMatchErrors []error
for _, matcher := range matchers {
for _, matcher := range mock.request.matchers {
if matcherError := matcher(req, mock.request); matcherError != nil {
mockMatchErrors = append(mockMatchErrors, matcherError)
}
Expand Down Expand Up @@ -333,6 +335,11 @@ func (r *MockRequest) QueryPresent(key string) *MockRequest {
return r
}

func (r *MockRequest) AddMatcher(matcher Matcher) *MockRequest {
r.matchers = append(r.matchers, matcher)
return r
}

func (r *MockRequest) RespondWith() *MockResponse {
return r.mock.response
}
Expand Down Expand Up @@ -555,7 +562,7 @@ func errorOrNil(statement bool, errorMessage func() string) error {
return errors.New(errorMessage())
}

var matchers = []Matcher{
var defaultMatchers = []Matcher{
pathMatcher,
hostMatcher,
schemeMatcher,
Expand Down
102 changes: 102 additions & 0 deletions mocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,77 @@ func TestMocks_PathMatcher(t *testing.T) {
}
}

func TestMocks_AddMatcher(t *testing.T) {
tests := map[string]struct {
matcherResponse error
mockResponse *MockResponse
matchErrors error
}{
"match": {
matcherResponse: nil,
mockResponse: &MockResponse{
body: `{"ok": true}`,
statusCode: 200,
times: 1,
},
matchErrors: nil,
},
"no match": {
matcherResponse: errors.New("nope"),
mockResponse: nil,
matchErrors: &unmatchedMockError{errors: map[int][]error{
1: {errors.New("nope")},
}},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/test/mock", nil)
matcher := func(r *http.Request, mr *MockRequest) error {
return test.matcherResponse
}

testMock := NewMock().
Get("/test/mock").
AddMatcher(matcher).
RespondWith().
Body(`{"ok": true}`).
Status(http.StatusOK).
End()

mockResponse, matchErrors := matches(req, []*Mock{testMock})

assert.Equal(t, test.matchErrors, matchErrors)
if test.mockResponse == nil {
assert.Nil(t, mockResponse)
} else {
assert.Equal(t, test.mockResponse.body, mockResponse.body)
assert.Equal(t, test.mockResponse.statusCode, mockResponse.statusCode)
assert.Equal(t, test.mockResponse.times, mockResponse.times)
}
})
}
}

func TestMocks_AddMatcher_KeepsDefaultMocks(t *testing.T) {
testMock := NewMock()

// Default matchers present on new mock
assert.Equal(t, 8, len(testMock.request.matchers))

testMock.Get("/test/mock").
AddMatcher(func(r *http.Request, mr *MockRequest) error {
return nil
}).
RespondWith().
Body(`{"ok": true}`).
Status(http.StatusOK).
End()

// New matcher added successfully
assert.Equal(t, 9, len(testMock.request.matchers))
}

func TestMocks_PanicsIfUrlInvalid(t *testing.T) {
defer func() {
if r := recover(); r == nil {
Expand Down Expand Up @@ -275,6 +346,37 @@ func TestMocks_Matches(t *testing.T) {
assert.Equal(t, `{"is_contactable": true}`, mockResponse.body)
}

func TestMocks_Matches_Errors(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/test/mock", nil)

testMock := NewMock().
Post("/test/mock").
Body(`{"bodyKey":"bodyVal"}`).
Query("queryKey", "queryVal").
QueryPresent("queryKey2").
QueryParams(map[string]string{"queryKey": "queryVal"}).
Header("headerKey", "headerVal").
Headers(map[string]string{"headerKey": "headerVal"}).
RespondWith().
Header("responseHeaderKey", "responseHeaderVal").
Body(`{"responseBodyKey": "responseBodyVal"}`).
Status(http.StatusOK).
End()

mockResponse, matchErrors := matches(req, []*Mock{testMock})

assert.Nil(t, mockResponse)
assert.Equal(t, &unmatchedMockError{errors: map[int][]error{
1: {
errors.New("received method GET did not match mock method POST"),
errors.New("not all of received headers map[] matched expected mock headers map[Headerkey:[headerVal headerVal]]"),
errors.New("not all of received query params map[] matched expected mock query params map[queryKey:[queryVal queryVal]]"),
errors.New("expected query param queryKey2 not received"),
errors.New("expected a body but received none"),
},
}}, matchErrors)
}

func TestMocks_Matches_NilIfNoMatch(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/preferences/12345", nil)

Expand Down

0 comments on commit 548b788

Please sign in to comment.