diff --git a/README.md b/README.md index 7d603c3..bb51abc 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,28 @@ See [Releases](https://github.com/maxcnunes/httpfake/releases) for detailed hist See [godoc reference](https://godoc.org/github.com/maxcnunes/httpfake) for detailed API documentation. +## Assertions + +There are built-in methods you can use to make assertions about requests to your HTTP handlers. The currently +supported assertions are: + +* Presence of query parameters +* Query parameter and its expected value +* Presence of HTTP headers +* HTTP header and its expected value +* The expected body of your request + +[WithTesting](https://godoc.org/github.com/maxcnunes/httpfake#WithTesting) **must** be provided as a server +option when creating the test server if intend to set request assertions. Failing to set the option +when using request assertions will result in a panic. + +### Custom Assertions + +You can also provide your own request assertions by creating a type that implements the +[Assertor interface](https://godoc.org/github.com/maxcnunes/httpfake#Assertor). The `Assertor.Log` method will be +called for each assertion before it's processed. The `Assertor.Error` method will only be called if the +`Assertor.Assert` method returns an error. + ## Examples For a full list of examples please check out the [functional_tests folder](/functional_tests). diff --git a/assertions.go b/assertions.go new file mode 100644 index 0000000..94bbbfd --- /dev/null +++ b/assertions.go @@ -0,0 +1,170 @@ +package httpfake + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" +) + +const assertErrorTemplate = "assertion error: %s" + +// Assertor provides an interface for setting assertions for http requests +type Assertor interface { + Assert(r *http.Request) error + Log(t testing.TB) + Error(t testing.TB, err error) +} + +// requiredHeaders provides an Assertor for the presence of the provided http header keys +type requiredHeaders struct { + Keys []string +} + +// Assert runs the required headers assertion against the provided request +func (h *requiredHeaders) Assert(r *http.Request) error { + var missingHeaders []string + + for _, key := range h.Keys { + if value := r.Header.Get(key); len(value) == 0 { + missingHeaders = append(missingHeaders, key) + } + } + + if len(missingHeaders) > 0 { + return fmt.Errorf("missing required header(s): %s", strings.Join(missingHeaders, ", ")) + } + + return nil +} + +// Log prints a testing info log for the requiredHeaders Assertor +func (h *requiredHeaders) Log(t testing.TB) { + t.Log("Testing request for required headers") +} + +// Error prints a testing error for the requiredHeaders Assertor +func (h *requiredHeaders) Error(t testing.TB, err error) { + t.Errorf(assertErrorTemplate, err) +} + +// requiredHeaderValue provides an Assertor for a header and its expected value +type requiredHeaderValue struct { + Key string + ExpectedValue string +} + +// Assert runs the required header value assertion against the provided request +func (h *requiredHeaderValue) Assert(r *http.Request) error { + if value := r.Header.Get(h.Key); value != h.ExpectedValue { + return fmt.Errorf("header %s does not have the expected value; expected %s to equal %s", + h.Key, + value, + h.ExpectedValue) + } + + return nil +} + +// Log prints a testing info log for the requiredHeaderValue Assertor +func (h *requiredHeaderValue) Log(t testing.TB) { + t.Logf("Testing request for required header value [%s: %s]", h.Key, h.ExpectedValue) +} + +// Error prints a testing error for the requiredHeaderValue Assertor +func (h *requiredHeaderValue) Error(t testing.TB, err error) { + t.Errorf(assertErrorTemplate, err) +} + +// requiredQueries provides an Assertor for the presence of the provided query parameter keys +type requiredQueries struct { + Keys []string +} + +// Assert runs the required queries assertion against the provided request +func (q *requiredQueries) Assert(r *http.Request) error { + queryVals := r.URL.Query() + var missingParams []string + + for _, key := range q.Keys { + if value := queryVals.Get(key); len(value) == 0 { + missingParams = append(missingParams, key) + } + } + if len(missingParams) > 0 { + return fmt.Errorf("missing required query parameter(s): %s", strings.Join(missingParams, ", ")) + } + + return nil +} + +// Log prints a testing info log for the requiredQueries Assertor +func (q *requiredQueries) Log(t testing.TB) { + t.Log("Testing request for required query parameters") +} + +// Error prints a testing error for the requiredQueries Assertor +func (q *requiredQueries) Error(t testing.TB, err error) { + t.Errorf(assertErrorTemplate, err) +} + +// requiredQueryValue provides an Assertor for a query parameter and its expected value +type requiredQueryValue struct { + Key string + ExpectedValue string +} + +// Assert runs the required query value assertion against the provided request +func (q *requiredQueryValue) Assert(r *http.Request) error { + if value := r.URL.Query().Get(q.Key); value != q.ExpectedValue { + return fmt.Errorf("query %s does not have the expected value; expected %s to equal %s", q.Key, value, q.ExpectedValue) + } + return nil +} + +// Log prints a testing info log for the requiredQueryValue Assertor +func (q *requiredQueryValue) Log(t testing.TB) { + t.Logf("Testing request for required query parameter value [%s: %s]", q.Key, q.ExpectedValue) +} + +// Error prints a testing error for the requiredQueryValue Assertor +func (q *requiredQueryValue) Error(t testing.TB, err error) { + t.Errorf(assertErrorTemplate, err) +} + +// requiredBody provides an Assertor for the expected value of the request body +type requiredBody struct { + ExpectedBody []byte +} + +// Assert runs the required body assertion against the provided request +func (b *requiredBody) Assert(r *http.Request) error { + if r.Body == nil { + return fmt.Errorf("error reading the request body; the request body is nil") + } + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return fmt.Errorf("error reading the request body: %w", err) + } + + if !bytes.EqualFold(b.ExpectedBody, body) { + return fmt.Errorf("request body does not have the expected value; expected %s to equal %s", + string(body[:]), + string(b.ExpectedBody[:])) + } + + return nil +} + +// Log prints a testing info log for the requiredBody Assertor +func (b *requiredBody) Log(t testing.TB) { + t.Log("Testing request for required a required body") +} + +// Error prints a testing error for the requiredBody Assertor +func (b *requiredBody) Error(t testing.TB, err error) { + t.Errorf(assertErrorTemplate, err) +} diff --git a/assertions_test.go b/assertions_test.go new file mode 100644 index 0000000..7a23e84 --- /dev/null +++ b/assertions_test.go @@ -0,0 +1,416 @@ +// nolint dupl gocyclo +package httpfake + +import ( + "bytes" + "errors" + "fmt" + "net/http" + "net/url" + "testing" +) + +type mockTester struct { + testing.TB + buf *bytes.Buffer +} + +func (t *mockTester) Log(args ...interface{}) { + t.buf.WriteString(fmt.Sprintln(args...)) +} + +func (t *mockTester) Logf(format string, args ...interface{}) { + t.buf.WriteString(fmt.Sprintf(format, args...)) +} + +func (t *mockTester) Errorf(format string, args ...interface{}) { + t.buf.WriteString(fmt.Sprintf(format, args...)) +} + +func TestAssertors_Assert(t *testing.T) { + tests := []struct { + name string + assertor Assertor + requestBuilder func() (*http.Request, error) + expectedErr string + }{ + { + name: "requiredHeaders should return no error with a proper request", + assertor: &requiredHeaders{ + Keys: []string{"test-header-1", "test-header-2"}, + }, + requestBuilder: func() (*http.Request, error) { + testReq, err := http.NewRequest(http.MethodPost, "http://fake.url", nil) + if err != nil { + return nil, err + } + + testReq.Header.Set("test-header-1", "mock-value-1") + testReq.Header.Set("test-header-2", "mock-value-2") + + return testReq, nil + }, + expectedErr: "", + }, + { + name: "requiredHeaders should return an error if a request is missing a required header", + assertor: &requiredHeaders{ + Keys: []string{"test-header-1", "test-header-2"}, + }, + requestBuilder: func() (*http.Request, error) { + testReq, err := http.NewRequest(http.MethodPost, "http://fake.url", nil) + if err != nil { + return nil, err + } + + testReq.Header.Set("test-header-2", "mock-value-2") + + return testReq, nil + }, + expectedErr: "missing required header(s): test-header-1", + }, + { + name: "requiredHeaderValue should return no error with a proper request", + assertor: &requiredHeaderValue{ + Key: "test-header-1", + ExpectedValue: "mock-value-1", + }, + requestBuilder: func() (*http.Request, error) { + testReq, err := http.NewRequest(http.MethodPost, "http://fake.url", nil) + if err != nil { + return nil, err + } + + testReq.Header.Set("test-header-1", "mock-value-1") + + return testReq, nil + }, + expectedErr: "", + }, + { + name: "requiredHeaderValue should return an error if a request is missing a required header", + assertor: &requiredHeaderValue{ + Key: "test-header-1", + ExpectedValue: "mock-value-1", + }, + requestBuilder: func() (*http.Request, error) { + testReq, err := http.NewRequest(http.MethodPost, "http://fake.url", nil) + if err != nil { + return nil, err + } + + return testReq, nil + }, + expectedErr: "header test-header-1 does not have the expected value; expected to equal mock-value-1", + }, + { + name: "requiredQueries should return no error with a proper request", + assertor: &requiredQueries{ + Keys: []string{"query-1", "query-2"}, + }, + requestBuilder: func() (*http.Request, error) { + u := "http://fake.url?query-1=apples&query-2=oranges" + mockURL, err := url.Parse(u) + if err != nil { + return nil, err + } + + testReq, err := http.NewRequest(http.MethodPost, mockURL.Host, nil) + if err != nil { + return nil, err + } + testReq.URL = mockURL + + return testReq, nil + }, + expectedErr: "", + }, + { + name: "requiredQueries should return an error if a request is missing the a required query params", + assertor: &requiredQueries{ + Keys: []string{"query-1", "query-3"}, + }, + requestBuilder: func() (*http.Request, error) { + u := "http://fake.url?query-2=oranges" + mockURL, err := url.Parse(u) + if err != nil { + return nil, err + } + + testReq, err := http.NewRequest(http.MethodPost, mockURL.Host, nil) + if err != nil { + return nil, err + } + testReq.URL = mockURL + + return testReq, nil + }, + expectedErr: "missing required query parameter(s): query-1, query-3", + }, + { + name: "requiredQueryValue should return no error with a proper request", + assertor: &requiredQueryValue{ + Key: "query-1", + ExpectedValue: "value-1", + }, + requestBuilder: func() (*http.Request, error) { + u := "http://fake.url?query-1=value-1" + mockURL, err := url.Parse(u) + if err != nil { + return nil, err + } + + testReq, err := http.NewRequest(http.MethodPost, mockURL.Host, nil) + if err != nil { + return nil, err + } + testReq.URL = mockURL + + return testReq, nil + }, + expectedErr: "", + }, + { + name: "requiredQueryValue should return an error if a request is missing the a required query param", + assertor: &requiredQueryValue{ + Key: "query-1", + ExpectedValue: "apples", + }, + requestBuilder: func() (*http.Request, error) { + u := "http://fake.url" + mockURL, err := url.Parse(u) + if err != nil { + return nil, err + } + + testReq, err := http.NewRequest(http.MethodPost, mockURL.Host, nil) + if err != nil { + return nil, err + } + testReq.URL = mockURL + + return testReq, nil + }, + expectedErr: "query query-1 does not have the expected value; expected to equal apples", + }, + { + name: "requiredQueryValue should return an error if a request has an incorrect query param value", + assertor: &requiredQueryValue{ + Key: "query-1", + ExpectedValue: "apples", + }, + requestBuilder: func() (*http.Request, error) { + u := "http://fake.url?query-1=oranges" + mockURL, err := url.Parse(u) + if err != nil { + return nil, err + } + + testReq, err := http.NewRequest(http.MethodPost, mockURL.Host, nil) + if err != nil { + return nil, err + } + testReq.URL = mockURL + + return testReq, nil + }, + expectedErr: "query query-1 does not have the expected value; expected oranges to equal apples", + }, + { + name: "requiredBody should return no error with a proper request", + assertor: &requiredBody{ + ExpectedBody: []byte(`{"testObj": {"data": {"fakeData": "testdata"}}}`), + }, + requestBuilder: func() (*http.Request, error) { + reader := bytes.NewBuffer([]byte(`{"testObj": {"data": {"fakeData": "testdata"}}}`)) + + testReq, err := http.NewRequest(http.MethodPost, "http://fake.url", reader) + if err != nil { + return nil, err + } + + return testReq, nil + }, + expectedErr: "", + }, + { + name: "requiredBody should return an error if the body is not what's expected", + assertor: &requiredBody{ + ExpectedBody: []byte(`{"testObj": {"data": {"fakeData": "testdata"}}}`), + }, + requestBuilder: func() (*http.Request, error) { + reader := bytes.NewBuffer([]byte(`{"testObj": {"data": {"badData": "bad"}}}`)) + + testReq, err := http.NewRequest(http.MethodPost, "http://fake.url", reader) + if err != nil { + return nil, err + } + + return testReq, nil + }, + expectedErr: "request body does not have the expected value; expected {\"testObj\": {\"data\": {\"badData\": \"bad\"}}} to equal {\"testObj\": {\"data\": {\"fakeData\": \"testdata\"}}}", + }, + { + name: "requiredBody should handle a nil body without panic", + assertor: &requiredBody{ + ExpectedBody: []byte(`{"testObj": {"data": {"fakeData": "testdata"}}}`), + }, + requestBuilder: func() (*http.Request, error) { + + testReq, err := http.NewRequest(http.MethodPost, "http://fake.url", nil) + if err != nil { + return nil, err + } + + return testReq, nil + }, + expectedErr: "error reading the request body; the request body is nil", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testReq, err := tt.requestBuilder() + if err != nil { + t.Fatalf("error setting up fake request: %#v", err) + } + + err = tt.assertor.Assert(testReq) + if len(tt.expectedErr) > 0 { + if err == nil { + t.Errorf("Expected error %s but err was nil", tt.expectedErr) + return + } + + if err.Error() != tt.expectedErr { + t.Errorf("Assert() error = %v, expected error %s", err, tt.expectedErr) + } + return + } + + if err != nil { + t.Errorf("Unexpected error = %v", err) + } + }) + } +} + +func TestAssertors_Log(t *testing.T) { + tests := []struct { + name string + mockTester *mockTester + assertor Assertor + expected string + }{ + { + name: "requiredHeaders Log should log the expected output when called", + mockTester: &mockTester{ + buf: &bytes.Buffer{}, + }, + assertor: &requiredHeaders{}, + expected: "Testing request for required headers\n", + }, + { + name: "requiredHeaderValue Log should log the expected output when called", + mockTester: &mockTester{ + buf: &bytes.Buffer{}, + }, + assertor: &requiredHeaderValue{Key: "test-key", ExpectedValue: "test-value"}, + expected: "Testing request for required header value [test-key: test-value]", + }, + { + name: "requiredQueries Log should log the expected output when called", + mockTester: &mockTester{ + buf: &bytes.Buffer{}, + }, + assertor: &requiredQueries{}, + expected: "Testing request for required query parameters\n", + }, + { + name: "requiredQueryValue Log should log the expected output when called", + mockTester: &mockTester{ + buf: &bytes.Buffer{}, + }, + assertor: &requiredQueryValue{Key: "test-key", ExpectedValue: "test-value"}, + expected: "Testing request for required query parameter value [test-key: test-value]", + }, + { + name: "requiredBody Log should log the expected output when called", + mockTester: &mockTester{ + buf: &bytes.Buffer{}, + }, + assertor: &requiredBody{}, + expected: "Testing request for required a required body\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.assertor.Log(tt.mockTester) + + result := tt.mockTester.buf.String() + if result != tt.expected { + t.Errorf("Expected Log %s, actual %#v", tt.expected, result) + } + }) + } +} + +func TestAssertors_Error(t *testing.T) { + tests := []struct { + name string + mockTester *mockTester + assertor Assertor + expected string + }{ + { + name: "requiredHeaders Log should log the expected output when called", + mockTester: &mockTester{ + buf: &bytes.Buffer{}, + }, + assertor: &requiredHeaders{}, + expected: "assertion error: test error", + }, + { + name: "requiredHeaderValue Log should log the expected output when called", + mockTester: &mockTester{ + buf: &bytes.Buffer{}, + }, + assertor: &requiredHeaderValue{Key: "test-key", ExpectedValue: "test-value"}, + expected: "assertion error: test error", + }, + { + name: "requiredQueries Log should log the expected output when called", + mockTester: &mockTester{ + buf: &bytes.Buffer{}, + }, + assertor: &requiredQueries{}, + expected: "assertion error: test error", + }, + { + name: "requiredQueryValue Log should log the expected output when called", + mockTester: &mockTester{ + buf: &bytes.Buffer{}, + }, + assertor: &requiredQueryValue{Key: "test-key", ExpectedValue: "test-value"}, + expected: "assertion error: test error", + }, + { + name: "requiredBody Log should log the expected output when called", + mockTester: &mockTester{ + buf: &bytes.Buffer{}, + }, + assertor: &requiredBody{}, + expected: "assertion error: test error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testErr := errors.New("test error") + tt.assertor.Error(tt.mockTester, testErr) + + result := tt.mockTester.buf.String() + if result != tt.expected { + t.Errorf("Expected Error %s, actual %#v", tt.expected, result) + } + }) + } +} diff --git a/functional_tests/simple_get_with_testing_test.go b/functional_tests/simple_get_with_testing_test.go new file mode 100644 index 0000000..996382e --- /dev/null +++ b/functional_tests/simple_get_with_testing_test.go @@ -0,0 +1,43 @@ +// nolint dupl +package functional_tests + +import ( + "io/ioutil" + "net/http" + "testing" + + "github.com/maxcnunes/httpfake" +) + +// TestSimpleGetWithTesting tests a fake server handling a GET request +func TestSimpleGetWithTesting(t *testing.T) { + fakeService := httpfake.New(httpfake.WithTesting(t)) + defer fakeService.Close() + + // register a handler for our fake service + fakeService.NewHandler(). + Get("/users?movie=dreamers"). + AssertQueryValue("movie", "dreamers"). + Reply(200). + BodyString(`[{"username": "dreamer","movie": "dreamers"}]`) + + res, err := http.Get(fakeService.ResolveURL("/users?movie=dreamers")) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() // nolint errcheck + + // Check the status code is what we expect + if status := res.StatusCode; status != 200 { + t.Errorf("request returned wrong status code: got %v want %v", + status, 200) + } + + // Check the response body is what we expect + expected := `[{"username": "dreamer","movie": "dreamers"}]` + body, _ := ioutil.ReadAll(res.Body) + if bodyString := string(body); bodyString != expected { + t.Errorf("request returned unexpected body: got %v want %v", + bodyString, expected) + } +} diff --git a/functional_tests/simple_post_with_testing_test.go b/functional_tests/simple_post_with_testing_test.go new file mode 100644 index 0000000..b8a91fa --- /dev/null +++ b/functional_tests/simple_post_with_testing_test.go @@ -0,0 +1,58 @@ +// nolint dupl +package functional_tests + +import ( + "bytes" + "io/ioutil" + "net/http" + "testing" + "time" + + "github.com/maxcnunes/httpfake" +) + +// TestSimplePostWithTesting tests a fake server handling a POST request +func TestSimplePostWithTesting(t *testing.T) { + fakeService := httpfake.New(httpfake.WithTesting(t)) + defer fakeService.Server.Close() + + // register a handler for our fake service + fakeService.NewHandler(). + Post("/users"). + AssertHeaders("Authorization"). + AssertHeaderValue("Content-Type", "application/json"). + Reply(201). + BodyString(`{"id": 1, "username": "dreamer"}`) + + client := &http.Client{ + Timeout: 5 * time.Second, + } + + sendBody := bytes.NewBuffer([]byte(`{"username": "dreamer"}`)) + req, err := http.NewRequest(http.MethodPost, fakeService.ResolveURL("/users"), sendBody) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Authorization", "Bearer some-token") + req.Header.Set("Content-Type", "application/json") + + res, err := client.Do(req) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() // nolint errcheck + + // Check the status code is what we expect + if status := res.StatusCode; status != 201 { + t.Errorf("request returned wrong status code: got %v want %v", + status, 201) + } + + // Check the response body is what we expect + expected := `{"id": 1, "username": "dreamer"}` + body, _ := ioutil.ReadAll(res.Body) + if bodyString := string(body); bodyString != expected { + t.Errorf("request returned unexpected body: got %v want %v", + bodyString, expected) + } +} diff --git a/functional_tests/simple_put_with_testing_test.go b/functional_tests/simple_put_with_testing_test.go new file mode 100644 index 0000000..79baacd --- /dev/null +++ b/functional_tests/simple_put_with_testing_test.go @@ -0,0 +1,50 @@ +// nolint dupl +package functional_tests + +import ( + "bytes" + "io/ioutil" + "net/http" + "testing" + + "github.com/maxcnunes/httpfake" +) + +// TestSimplePutWithTesting tests a fake server handling a PUT request +func TestSimplePutWithTesting(t *testing.T) { + fakeService := httpfake.New(httpfake.WithTesting(t)) + defer fakeService.Close() + + // register a handler for our fake service + fakeService.NewHandler(). + Put("/users/1"). + AssertBody([]byte(`{"username": "dreamer"}`)). + Reply(200). + BodyString(`{"id": 1,"username": "dreamer"}`) + + sendBody := bytes.NewBuffer([]byte(`{"username": "dreamer"}`)) + req, err := http.NewRequest("PUT", fakeService.ResolveURL("/users/1"), sendBody) + if err != nil { + t.Fatal(err) + } + + res, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() // nolint errcheck + + // Check the status code is what we expect + if status := res.StatusCode; status != 200 { + t.Errorf("request returned wrong status code: got %v want %v", + status, 200) + } + + // Check the response body is what we expect + expected := `{"id": 1,"username": "dreamer"}` + body, _ := ioutil.ReadAll(res.Body) + if bodyString := string(body); bodyString != expected { + t.Errorf("request returned unexpected body: got %v want %v", + bodyString, expected) + } +} diff --git a/httpfake.go b/httpfake.go index eef9a29..fbbd648 100644 --- a/httpfake.go +++ b/httpfake.go @@ -11,21 +11,46 @@ import ( "net/http/httptest" netURL "net/url" "strings" + "testing" ) // HTTPFake is the root struct for the fake server type HTTPFake struct { Server *httptest.Server RequestHandlers []*Request + t *testing.T +} + +// ServerOption provides a functional signature for providing configuration options to the fake server +type ServerOption func(opts *ServerOptions) + +// ServerOptions a configuration object for the fake test server +type ServerOptions struct { + t *testing.T +} + +// WithTesting returns a configuration function that allows you to configure the testing object on the fake server. +// The testing object is utilized for assertions set on the request object and will throw a testing error if an +// endpoint is not called. +func WithTesting(t *testing.T) ServerOption { + return func(opts *ServerOptions) { + opts.t = t + } } // New starts a httptest.Server as the fake server // and sets up the initial configuration to this server's request handlers -func New() *HTTPFake { +func New(opts ...ServerOption) *HTTPFake { fake := &HTTPFake{ RequestHandlers: []*Request{}, } + var serverOpts ServerOptions + for _, opt := range opts { + opt(&serverOpts) + } + + fake.t = serverOpts.t fake.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rh, err := fake.findHandler(r) if err != nil { @@ -47,6 +72,17 @@ func New() *HTTPFake { return } + rh.called++ + + if rh.assertions != nil { + if fake.t == nil { + errMsg := fmt.Sprintf("setup error: \"WithTesting\" is required when assertions are set") + panic(errMsg) + } + + rh.runAssertions(fake.t, r) + } + if rh.CustomHandle != nil { rh.CustomHandle(w, r, rh) return @@ -77,6 +113,21 @@ func (f *HTTPFake) Reset() *HTTPFake { return f } +// Close shuts down the HTTP Test server, this will block until all outstanding requests on the server have completed. +// If the WithTesting option was specified when setting up the server Close will assert that each http handler +// specified for this server was called +func (f *HTTPFake) Close() { + defer f.Server.Close() + + if f.t != nil { + for _, reqHandler := range f.RequestHandlers { + if reqHandler.called == 0 { + f.t.Errorf("httpfake: request handler was specified but not called %s", reqHandler.URL.Path) + } + } + } +} + func (f *HTTPFake) findHandler(r *http.Request) (*Request, error) { founds := []*Request{} url := r.URL.String() diff --git a/request.go b/request.go index 91e88d2..3856948 100644 --- a/request.go +++ b/request.go @@ -1,8 +1,10 @@ package httpfake import ( + "net/http" "net/url" "strings" + "testing" ) // Request stores the settings for a request handler @@ -13,6 +15,8 @@ type Request struct { URL *url.URL Response *Response CustomHandle Responder + assertions []Assertor + called int } // NewRequest creates a new Request @@ -20,6 +24,7 @@ func NewRequest() *Request { return &Request{ URL: &url.URL{}, Response: NewResponse(), + called: 0, } } @@ -72,3 +77,48 @@ func (r *Request) method(method, path string) *Request { r.Method = strings.ToUpper(method) return r } + +func (r *Request) runAssertions(t *testing.T, testReq *http.Request) { + for _, assertor := range r.assertions { + assertor.Log(t) + if err := assertor.Assert(testReq); err != nil { + assertor.Error(t, err) + } + } +} + +// AssertQueries will assert that the provided query parameters are present in the requests to this handler +func (r *Request) AssertQueries(key ...string) *Request { + r.assertions = append(r.assertions, &requiredQueries{Keys: key}) + return r +} + +// AssertQueryValue will assert that the provided query parameter and value are present in the requests to this handler +func (r *Request) AssertQueryValue(key, value string) *Request { + r.assertions = append(r.assertions, &requiredQueryValue{Key: key, ExpectedValue: value}) + return r +} + +// AssertHeaders will assert that the provided header keys are present in the requests to this handler +func (r *Request) AssertHeaders(keys ...string) *Request { + r.assertions = append(r.assertions, &requiredHeaders{Keys: keys}) + return r +} + +// AssertHeaderValue will assert that the provided header key and value are present in the requests to this handler +func (r *Request) AssertHeaderValue(key, value string) *Request { + r.assertions = append(r.assertions, &requiredHeaderValue{Key: key, ExpectedValue: value}) + return r +} + +// AssertBody will assert that that the provided body matches in the requests to this handler +func (r *Request) AssertBody(body []byte) *Request { + r.assertions = append(r.assertions, &requiredBody{ExpectedBody: body}) + return r +} + +// AssertCustom will run the provided assertor against requests to this handler +func (r *Request) AssertCustom(assertor Assertor) *Request { + r.assertions = append(r.assertions, assertor) + return r +}