-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathhttpmock.go
188 lines (153 loc) · 5.57 KB
/
httpmock.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/*
Package httpmock builds on httptest, providing easier API mocking.
Essentially all httpmock does is implement a similar interface to httptest, but using a Handler that receives the HTTP
method, path, and body rather than a request object. This makes it very easy to use a featureful mock as the handler,
e.g. github.com/stretchr/testify/mock
Examples
s := httpmock.NewServer(&httpmock.OKHandler{})
defer s.Close()
// Make any requests you want to s.URL(), using it as the mock downstream server
This example uses MockHandler, a Handler that is a github.com/stretchr/testify/mock object.
downstream := httpmock.NewMockHandler(t)
// A simple GET that returns some pre-canned content
downstream.On("Handle", "GET", "/object/12345", mock.Anything).Return(httpmock.Response{
Body: []byte(`{"status": "ok"}`),
})
s := httpmock.NewServer(downstream)
defer s.Close()
//
// Make any requests you want to s.URL(), using it as the mock downstream server
//
downstream.AssertExpectations(t)
If instead you wish to match against headers as well, a slightly different httpmock object can be used
(please note the change in function name to be matched against):
downstream := httpmock.NewMockHandlerWithHeaders(t)
// A simple GET that returns some pre-canned content
downstream.On("HandleWithHeaders", "GET", "/object/12345", MatchHeader("MOCK", "this"), mock.Anything).Return(httpmock.Response{
Body: []byte(`{"status": "ok"}`),
})
// ... same as above
Httpmock also provides helpers for checking calls using json objects, like so:
// This tests a hypothetical "echo" endpoint, which returns the body we pass to it.
type Obj struct {
A string `json:"a"`
B string `json:"b"`
}
o := &Obj{A: "ay", B: "bee"}
// JSONMatcher ensures that this mock is triggered only when the HTTP body, when deserialized, matches the given
// object.
downstream.On("Handle", "POST", "/echo", httpmock.JSONMatcher(o)).Return(httpmock.Response{
Body: httpmock.ToJSON(o),
})
*/
package httpmock
import (
"io"
"log"
"net/http"
"net/http/httptest"
"testing"
)
// Handler is the interface used by httpmock instead of http.Handler so that it can be mocked very easily.
type Handler interface {
Handle(method, path string, body []byte) Response
}
// HandlerWithHeaders is the interface used by httpmock instead of http.Handler so that it can be mocked very easily,
// it additionally allows matching on headers.
type HandlerWithHeaders interface {
Handler
HandleWithHeaders(method, path string, headers http.Header, body []byte) Response
}
// NewMockHandler returns a pointer to a new mock handler with the test struct set
func NewMockHandler(t *testing.T) *MockHandler {
handler := &MockHandler{}
handler.Test(t)
return handler
}
// NewMockHandlerWithHeaders returns a pointer to a new mock handler with headers with the test struct set
func NewMockHandlerWithHeaders(t *testing.T) *MockHandlerWithHeaders {
handler := &MockHandlerWithHeaders{}
handler.Test(t)
return handler
}
// Response holds the response a handler wants to return to the client.
type Response struct {
// The HTTP status code to write (default: 200)
Status int
// Headers to add to the response
Header http.Header
// The response body to write (default: no body)
Body []byte
}
// Server listens for requests and interprets them into calls to your Handler.
type Server struct {
httpServer *httptest.Server
}
// NewServer constructs a new server and starts it (compare to httptest.NewServer). It needs to be Closed()ed.
// If you pass a handler that conforms to the HandlerWithHeaders interface, when requests are received, the
// HandleWithHeaders method will be called rather than Handle.
func NewServer(handler Handler) *Server {
s := NewUnstartedServer(handler)
s.Start()
return s
}
// NewUnstartedServer constructs a new server but doesn't start it (compare to httptest.NewUnstartedServer).
// If you pass a handler that conforms to the HandlerWithHeaders interface, when requests are received, the
// HandleWithHeaders method will be called rather than Handle.
func NewUnstartedServer(handler Handler) *Server {
converter := &httpToHTTPMockHandler{}
if hh, ok := handler.(HandlerWithHeaders); ok {
converter.handlerWithHeaders = hh
} else {
converter.handler = handler
}
s := &Server{
httpServer: httptest.NewUnstartedServer(converter),
}
return s
}
// Start starts an unstarted server.
func (s *Server) Start() {
s.httpServer.Start()
}
// Close shuts down a started server.
func (s *Server) Close() {
s.httpServer.Close()
}
// URL is the URL for the local test server, i.e. the value of httptest.Server.URL
func (s *Server) URL() string {
return s.httpServer.URL
}
// httpToHTTPMockHandler is a normal http.Handler that converts the request into a httpmock.Handler call and calls the
// httmock handler.
type httpToHTTPMockHandler struct {
handler Handler
handlerWithHeaders HandlerWithHeaders
}
// ServeHTTP makes this implement http.Handler
func (h *httpToHTTPMockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
log.Printf("Failed to read HTTP body in httpmock: %v", err)
}
var resp Response
if h.handler != nil {
resp = h.handler.Handle(r.Method, r.URL.RequestURI(), body)
} else {
resp = h.handlerWithHeaders.HandleWithHeaders(r.Method, r.URL.RequestURI(), r.Header, body)
}
for k, v := range resp.Header {
for _, val := range v {
w.Header().Add(k, val)
}
}
status := resp.Status
if status == 0 {
status = 200
}
w.WriteHeader(status)
_, err = w.Write(resp.Body)
if err != nil {
log.Printf("Failed to write response in httpmock: %v", err)
}
}