Skip to content
Permalink
Browse files

Add method to create a url encoded form body in the request. Also add…

… new mock matchers
  • Loading branch information...
enrico5b1b4 committed Jun 15, 2019
1 parent dd61149 commit 1120333c78d7721b433189ea3953d1c8151fdbfb
Showing with 381 additions and 18 deletions.
  1. +17 −0 README.md
  2. +34 −4 apitest.go
  3. +64 −0 apitest_test.go
  4. +18 −0 docs/content/docs/request.md
  5. +106 −14 mocks.go
  6. +142 −0 mocks_test.go
@@ -278,6 +278,23 @@ func TestApi(t *testing.T) {
}
```
#### Provide a url encoded form body in the request
```go
func TestApi(t *testing.T) {
apitest.New().
Handler(handler).
Post("/hello").
FormData("a", "1").
FormData("b", "2").
FormData("b", "3").
FormData("c", "4", "5", "6").
Expect(t).
Status(http.StatusOK).
End()
}
```
#### Capture the request and response data
```go
@@ -70,9 +70,10 @@ func New(name ...string) *APITest {
}

request := &Request{
apiTest: apiTest,
headers: map[string][]string{},
query: map[string][]string{},
apiTest: apiTest,
headers: map[string][]string{},
query: map[string][]string{},
formData: map[string][]string{},
}
response := &Response{
apiTest: apiTest,
@@ -187,6 +188,7 @@ type Request struct {
query map[string][]string
queryCollection map[string][]string
headers map[string][]string
formData map[string][]string
cookies []*Cookie
basicAuth string
apiTest *APITest
@@ -264,7 +266,7 @@ func (r *Request) Body(b string) *Request {
// JSON is a convenience method for setting the request body and content type header as "application/json"
func (r *Request) JSON(b string) *Request {
r.body = b
r.Header("Content-Type", "application/json")
r.ContentType("application/json")
return r
}

@@ -306,6 +308,13 @@ func (r *Request) Headers(headers map[string]string) *Request {
return r
}

// ContentType is a builder method to set the Content-Type header of the request
func (r *Request) ContentType(contentType string) *Request {
normalizedKey := textproto.CanonicalMIMEHeaderKey("Content-Type")
r.headers[normalizedKey] = []string{contentType}
return r
}

// Cookie is a convenience method for setting a single request cookies by name and value
func (r *Request) Cookie(name, value string) *Request {
r.cookies = append(r.cookies, &Cookie{name: &name, value: &value})
@@ -324,6 +333,17 @@ func (r *Request) BasicAuth(username, password string) *Request {
return r
}

// FormData is a builder method to set the body form data
// Also sets the content type of the request to application/x-www-form-urlencoded
func (r *Request) FormData(name string, values ...string) *Request {
r.ContentType("application/x-www-form-urlencoded")
for _, value := range values {
r.formData[name] = append(r.formData[name], value)
}

return r
}

// Expect marks the request spec as complete and following code will define the expected response
func (r *Request) Expect(t *testing.T) *Response {
r.apiTest.t = t
@@ -667,6 +687,16 @@ func (a *APITest) serveHttp(res *httptest.ResponseRecorder, req *http.Request) {
}

func (a *APITest) BuildRequest() *http.Request {
if len(a.request.formData) > 0 {
form := url.Values{}
for k := range a.request.formData {
for _, value := range a.request.formData[k] {
form.Add(k, value)
}
}
a.request.body = form.Encode()
}

req, _ := http.NewRequest(a.request.method, a.request.url, bytes.NewBufferString(a.request.body))
req.URL.RawQuery = formatQuery(a.request)
req.Host = "application"
@@ -187,6 +187,26 @@ func TestApiTest_AddsHeadersToRequest(t *testing.T) {
End()
}

func TestApiTest_AddsContentTypeHeaderToRequest(t *testing.T) {
handler := http.NewServeMux()
handler.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
if r.Header["Content-Type"][0] != "application/x-www-form-urlencoded" {
w.WriteHeader(http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
})

New().
Handler(handler).
Post("/hello").
ContentType("application/x-www-form-urlencoded").
Body(`name=John`).
Expect(t).
Status(http.StatusOK).
End()
}

func TestApiTest_AddsCookiesToRequest(t *testing.T) {
handler := http.NewServeMux()
handler.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
@@ -844,6 +864,50 @@ func TestRealNetworking(t *testing.T) {
<-finish
}

func TestApiTest_AddsUrlEncodedFormBody(t *testing.T) {
handler := http.NewServeMux()
handler.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
if r.Header["Content-Type"][0] != "application/x-www-form-urlencoded" {
w.WriteHeader(http.StatusBadRequest)
return
}

err := r.ParseForm()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}

expectedPostFormData := map[string][]string{
"name": {"John"},
"age": {"99"},
"children": {"Jack", "Ann"},
"pets": {"Toby", "Henry", "Alice"},
}

for key := range expectedPostFormData {
if !reflect.DeepEqual(expectedPostFormData[key], r.PostForm[key]) {
w.WriteHeader(http.StatusBadRequest)
return
}
}

w.WriteHeader(http.StatusOK)
})

New().
Handler(handler).
Post("/hello").
FormData("name", "John").
FormData("age", "99").
FormData("children", "Jack").
FormData("children", "Ann").
FormData("pets", "Toby", "Henry", "Alice").
Expect(t).
Status(http.StatusOK).
End()
}

type RecorderCaptor struct {
capturedRecorder Recorder
}
@@ -72,6 +72,24 @@ Header("name", "value")
Headers(map[string]string{"name1": "value1", "name2": "value2"})
```

## URL encoded form

There are multiple ways to create a URL encoded form body in the request. The following approaches are chainable.

### short form

```go
FormData("name", "value")
```

### multiple values

`FormData` is a variadic function that can be used to take a variable amount of values for the same key.

```go
FormData("name", "value1", "value2")
```

## Cookies

There are multiple ways to specify http request cookies. These approaches are chainable.
120 mocks.go
@@ -196,20 +196,23 @@ type Mock struct {
}

type MockRequest struct {
mock *Mock
url *url.URL
method string
headers map[string][]string
headerPresent []string
headerNotPresent []string
query map[string][]string
queryPresent []string
queryNotPresent []string
cookie []Cookie
cookiePresent []string
cookieNotPresent []string
body string
matchers []Matcher
mock *Mock
url *url.URL
method string
headers map[string][]string
headerPresent []string
headerNotPresent []string
formData map[string][]string
formDataPresent []string
formDataNotPresent []string
query map[string][]string
queryPresent []string
queryNotPresent []string
cookie []Cookie
cookiePresent []string
cookieNotPresent []string
body string
matchers []Matcher
}

type MockResponse struct {
@@ -261,6 +264,7 @@ func NewMock() *Mock {
req := &MockRequest{
mock: mock,
headers: map[string][]string{},
formData: map[string][]string{},
query: map[string][]string{},
matchers: defaultMatchers,
}
@@ -384,6 +388,23 @@ func (r *MockRequest) HeaderNotPresent(key string) *MockRequest {
return r
}

func (r *MockRequest) FormData(key string, values ...string) *MockRequest {
for _, value := range values {
r.formData[key] = append(r.formData[key], value)
}
return r
}

func (r *MockRequest) FormDataPresent(key string) *MockRequest {
r.formDataPresent = append(r.formDataPresent, key)
return r
}

func (r *MockRequest) FormDataNotPresent(key string) *MockRequest {
r.formDataNotPresent = append(r.formDataNotPresent, key)
return r
}

func (r *MockRequest) Query(key, value string) *MockRequest {
r.query[key] = append(r.query[key], value)
return r
@@ -636,6 +657,74 @@ var queryNotPresentMatcher = func(req *http.Request, spec *MockRequest) error {
return nil
}

var formDataMatcher = func(req *http.Request, spec *MockRequest) error {
mockFormData := spec.formData

for key, values := range mockFormData {
err := req.ParseForm()
if err != nil {
return errors.New("unable to parse form data")
}

receivedFormData := req.PostForm

if _, ok := receivedFormData[key]; !ok {
return fmt.Errorf("not all of received form data values %s matched expected mock form data values %s", receivedFormData, mockFormData)
}

found := 0
for _, field := range receivedFormData[key] {
for _, value := range values {
match, err := regexp.MatchString(value, field)
if err != nil {
return fmt.Errorf("unable to match received form data values %s against expected mock form data values %s", value, field)
}

if match {
found++
}
}
}

if found != len(values) {
return fmt.Errorf("not all of received form data values %s matched expected mock form data values %s", receivedFormData, mockFormData)
}
}
return nil
}

var formDataPresentMatcher = func(req *http.Request, spec *MockRequest) error {
err := req.ParseForm()
if err != nil {
return errors.New("unable to parse form data")
}

receivedFormData := req.PostForm

for _, key := range spec.formDataPresent {
if _, ok := receivedFormData[key]; !ok {
return fmt.Errorf("expected form data key %s not received", key)
}
}
return nil
}

var formDataNotPresentMatcher = func(req *http.Request, spec *MockRequest) error {
err := req.ParseForm()
if err != nil {
return errors.New("unable to parse form data")
}

receivedFormData := req.PostForm

for _, key := range spec.formDataNotPresent {
if _, ok := receivedFormData[key]; ok {
return fmt.Errorf("did not expect a form data key %s", key)
}
}
return nil
}

var cookieMatcher = func(req *http.Request, spec *MockRequest) error {
for _, c := range spec.cookie {
foundCookie, _ := req.Cookie(*c.name)
@@ -736,6 +825,9 @@ var defaultMatchers = []Matcher{
queryParamMatcher,
queryPresentMatcher,
queryNotPresentMatcher,
formDataMatcher,
formDataPresentMatcher,
formDataNotPresentMatcher,
bodyMatcher,
cookieMatcher,
cookiePresentMatcher,

0 comments on commit 1120333

Please sign in to comment.
You can’t perform that action at this time.