-
Notifications
You must be signed in to change notification settings - Fork 3
/
smock.go
138 lines (113 loc) · 4.49 KB
/
smock.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
/*
Package smock is a package for testing the outgoing http requests initiated from a function. The package creates a mock server and passes the URL to the function to be tested and then collects all the requests that the server received.
The server can be stopped after a certain amount of seconds, after a certain amount of requests, whenever it doesn't receive any new requests for a certain amount of time or any combination of the three.
*/
package smock
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"time"
"github.com/eapache/channels"
)
// The MockServer struct exposes the Reqs InfiniteChan of type MockRequest that you can use to get all the requests that the server received
type MockServer struct {
// The mock server instance
testServer *httptest.Server
config MockServerConfig
// The channel in which each new request received by the mock server will be passed to.
Reqs *channels.InfiniteChannel
}
// The MockRequest struct is what's send by the server whenever it receives a request.
type MockRequest struct {
// The received request body
Body string
// The headers of the received request
Headers http.Header
// The method of the received request
Method string
}
// MockServerConfig contains the configuration of the mock server
type MockServerConfig struct {
// The response code returned by the mock server to all incoming requests
ResponseStatusCode int
// The number of seconds to stop the mock server after
GlobalTimeout int
// The number of seconds to stop the mock server after if the server didn't receive any request within this period
RequestTimeout int
// Number of requests to close the server after
MaximumRequestCount int
}
// Start starts the mock server and initiates the requests channel
func (s *MockServer) Start() {
s.testServer.Start()
s.Reqs = channels.NewInfiniteChannel()
}
// Close stops the mock server and closes the requests channel
func (s *MockServer) Close() {
s.testServer.Close()
s.Reqs.Close()
s.Reqs = nil
}
// URL returns the URL of the created MockServer
func (s *MockServer) URL() string {
return s.testServer.URL
}
func mergeDefaultConfigs(config *MockServerConfig) {
if config.ResponseStatusCode == 0 {
config.ResponseStatusCode = 200
}
}
// NewMockServer returns a MockServer instance that you can use on your own or use the CaptureRequests function. At least one of: the GlobalTimeout, the RequestTimeout or MaximumRequestCount must be set.
// If to be used on your own mockServer.Start() should be called first before starting to use the server and mockServer.Close() should be called to stop the server.
func NewMockServer(config MockServerConfig) *MockServer {
mergeDefaultConfigs(&config)
if config.GlobalTimeout == 0 && config.RequestTimeout == 0 && config.MaximumRequestCount == 0 {
panic("At least one of: the GlobalTimeout, the RequestTimeout or MaximumRequestCount must be set")
}
server := MockServer{
config: config,
}
server.testServer = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(config.ResponseStatusCode)
body, _ := ioutil.ReadAll(r.Body)
r.Body.Close()
server.Reqs.In() <- MockRequest{
Body: string(body),
Headers: r.Header,
Method: r.Method,
}
}))
return &server
}
// CaptureRequests takes a function to be tested. It starts the mock server, runs the function passing the URL of the mock server to it, and any request that is sent to this URL is logged. Depending on the MockServerConfig, the server can be stopped by two ways. The first one is the global timeout which stops the server after a fixed number of seconds. The second one is a timeout between requests, if the server didn't receive any request within <config.RequestTimeout> seconds it will exit.
func (s *MockServer) CaptureRequests(f func(string)) []MockRequest {
s.Start()
defer s.Close()
f(s.URL())
// If the timeout is not set, they will be kept as a nil chan that block forever
var rtChan, gtChan <-chan time.Time
if s.config.GlobalTimeout > 0 {
gtChan = time.After(time.Second * time.Duration(s.config.GlobalTimeout))
}
if s.config.RequestTimeout > 0 {
rtChan = time.After(time.Second * time.Duration(s.config.RequestTimeout))
}
var ret []MockRequest
MainLoop:
for {
if s.config.MaximumRequestCount > 0 && len(ret) >= s.config.MaximumRequestCount {
break MainLoop
}
select {
case in := <-s.Reqs.Out():
request, _ := in.(MockRequest)
ret = append(ret, request)
case <-rtChan:
break MainLoop
case <-gtChan:
break MainLoop
}
}
return ret
}