Skip to content

Commit 2225990

Browse files
authored
feat: enhance Activate and add DeactivateNonDefault functions (#161)
Now requires go 1.16, to be able to use testing.T.Cleanup. Signed-off-by: Maxime Soulé <[email protected]>
1 parent ee13561 commit 2225990

File tree

7 files changed

+160
-106
lines changed

7 files changed

+160
-106
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
test:
1212
strategy:
1313
matrix:
14-
go-version: [1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x, tip]
14+
go-version: [1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x, tip]
1515
full-tests: [false]
1616
include:
1717
- go-version: 1.23.x

README.md

+4-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Easy mocking of http responses from external resources.
44

55
## Install
66

7-
Currently supports Go 1.13 to 1.23 and is regularly tested against tip.
7+
Currently supports Go 1.16 to 1.23 and is regularly tested against tip.
88

99
`v1` branch has to be used instead of `master`.
1010

@@ -23,8 +23,7 @@ populate your `go.mod` with the latest httpmock release, now
2323
### Simple Example:
2424
```go
2525
func TestFetchArticles(t *testing.T) {
26-
httpmock.Activate()
27-
t.Cleanup(httpmock.DeactivateAndReset)
26+
httpmock.Activate(t)
2827

2928
// Exact URL match
3029
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles",
@@ -51,8 +50,7 @@ func TestFetchArticles(t *testing.T) {
5150
### Advanced Example:
5251
```go
5352
func TestFetchArticles(t *testing.T) {
54-
httpmock.Activate()
55-
t.Cleanup(httpmock.DeactivateAndReset)
53+
httpmock.Activate(t)
5654

5755
// our database of articles
5856
articles := make([]map[string]interface{}, 0)
@@ -138,7 +136,7 @@ type MySuite struct{}
138136

139137
func (s *MySuite) Setup(t *td.T) error {
140138
// block all HTTP requests
141-
httpmock.Activate()
139+
httpmock.Activate(t)
142140
return nil
143141
}
144142

@@ -148,11 +146,6 @@ func (s *MySuite) PostTest(t *td.T, testName string) error {
148146
return nil
149147
}
150148

151-
func (s *MySuite) Destroy(t *td.T) error {
152-
httpmock.DeactivateAndReset()
153-
return nil
154-
}
155-
156149
func TestMySuite(t *testing.T) {
157150
tdsuite.Run(t, &MySuite{})
158151
}

env_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,6 @@ func TestEnv(t *testing.T) {
5353
"expected client1.Transport to not be our DefaultTransport")
5454
require.Not(client2.Transport, httpmock.DefaultTransport,
5555
"expected client2.Transport to not be our DefaultTransport")
56+
httpmock.DeactivateNonDefault(client1)
5657
httpmock.Deactivate()
5758
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ module github.com/jarcoal/httpmock
22

33
go 1.18
44

5-
require github.com/maxatome/go-testdeep v1.12.0
5+
require github.com/maxatome/go-testdeep v1.14.0
66

77
require github.com/davecgh/go-spew v1.1.1 // indirect

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
22
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3-
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
4-
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
3+
github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI=
4+
github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=

transport.go

+119-58
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strconv"
1313
"strings"
1414
"sync"
15+
"testing"
1516

1617
"github.com/jarcoal/httpmock/internal"
1718
)
@@ -1096,10 +1097,17 @@ var oldClientsLock sync.Mutex
10961097
// To enable mocks for a test, simply activate at the beginning of a test:
10971098
//
10981099
// func TestFetchArticles(t *testing.T) {
1099-
// httpmock.Activate()
1100+
// httpmock.Activate(t)
11001101
// // all http requests using http.DefaultTransport will now be intercepted
11011102
// }
11021103
//
1104+
// t is optional, when present it allows to automatically call
1105+
// [DeactivateAndReset] at the end of the test. It is strictly
1106+
// equivalent to:
1107+
//
1108+
// httpmock.Activate()
1109+
// t.Cleanup(httpmock.DeactivateAndReset)
1110+
//
11031111
// If you want all of your tests in a package to be mocked, just call
11041112
// [Activate] from init():
11051113
//
@@ -1113,7 +1121,7 @@ var oldClientsLock sync.Mutex
11131121
// httpmock.Activate()
11141122
// os.Exit(m.Run())
11151123
// }
1116-
func Activate() {
1124+
func Activate(t ...testing.TB) {
11171125
if Disabled() {
11181126
return
11191127
}
@@ -1124,18 +1132,30 @@ func Activate() {
11241132
InitialTransport = http.DefaultTransport
11251133
}
11261134

1135+
if len(t) > 0 && t[0] != nil {
1136+
t[0].Cleanup(DeactivateAndReset)
1137+
}
1138+
11271139
http.DefaultTransport = DefaultTransport
11281140
}
11291141

11301142
// ActivateNonDefault starts the mock environment with a non-default
1131-
// [*http.Client]. This emulates the [Activate] function, but allows for
1132-
// custom clients that do not use [http.DefaultTransport].
1143+
// [*http.Client]. This emulates the [Activate] function, but allows
1144+
// for custom clients that do not use [http.DefaultTransport]
1145+
// directly. To do so, it overrides the client Transport field with
1146+
// [DefaultTransport].
1147+
//
1148+
// To enable mocks for a test using a custom client, like:
11331149
//
1134-
// To enable mocks for a test using a custom client, activate at the
1135-
// beginning of a test:
1150+
// transport := http.DefaultTransport.(*http.Transport).Clone()
1151+
// transport.TLSHandshakeTimeout = 60 * time.Second
1152+
// client := &http.Client{Transport: transport}
1153+
//
1154+
// activate at the beginning of the test:
11361155
//
1137-
// client := &http.Client{Transport: &http.Transport{TLSHandshakeTimeout: 60 * time.Second}}
11381156
// httpmock.ActivateNonDefault(client)
1157+
//
1158+
// See also [DeactivateNonDefault], [Deactivate] and [DeactivateAndReset].
11391159
func ActivateNonDefault(client *http.Client) {
11401160
if Disabled() {
11411161
return
@@ -1150,63 +1170,56 @@ func ActivateNonDefault(client *http.Client) {
11501170
client.Transport = DefaultTransport
11511171
}
11521172

1153-
// GetCallCountInfo gets the info on all the calls httpmock has caught
1154-
// since it was activated or reset. The info is returned as a map of
1155-
// the calling keys with the number of calls made to them as their
1156-
// value. The key is the method, a space, and the URL all concatenated
1157-
// together.
1173+
// DeactivateNonDefault shuts down the mock environment for
1174+
// client. Any HTTP calls made after this will use the transport used
1175+
// before [ActivateNonDefault] call.
11581176
//
1159-
// As a special case, regexp responders generate 2 entries for each
1160-
// call. One for the call caught and the other for the rule that
1161-
// matched. For example:
1162-
//
1163-
// RegisterResponder("GET", `=~z\.com\z`, NewStringResponder(200, "body"))
1164-
// http.Get("http://z.com")
1165-
//
1166-
// will generate the following result:
1167-
//
1168-
// map[string]int{
1169-
// `GET http://z.com`: 1,
1170-
// `GET =~z\.com\z`: 1,
1171-
// }
1172-
func GetCallCountInfo() map[string]int {
1173-
return DefaultTransport.GetCallCountInfo()
1174-
}
1177+
// See also [Deactivate] and [DeactivateAndReset].
1178+
func DeactivateNonDefault(client *http.Client) {
1179+
if Disabled() {
1180+
return
1181+
}
11751182

1176-
// GetTotalCallCount gets the total number of calls httpmock has taken
1177-
// since it was activated or reset.
1178-
func GetTotalCallCount() int {
1179-
return DefaultTransport.GetTotalCallCount()
1183+
oldClientsLock.Lock()
1184+
defer oldClientsLock.Unlock()
1185+
if tr, ok := oldClients[client]; ok {
1186+
delete(oldClients, client)
1187+
client.Transport = tr
1188+
}
11801189
}
11811190

11821191
// Deactivate shuts down the mock environment. Any HTTP calls made
11831192
// after this will use a live transport.
11841193
//
1185-
// Usually you'll call it in a defer right after activating the mock
1186-
// environment:
1194+
// Usually you'll call it in a [testing.T.Cleanup] right after
1195+
// activating the mock environment:
11871196
//
11881197
// func TestFetchArticles(t *testing.T) {
11891198
// httpmock.Activate()
1190-
// defer httpmock.Deactivate()
1199+
// t.Cleanup(httpmock.Deactivate)
11911200
//
11921201
// // when this test ends, the mock environment will close
11931202
// }
11941203
//
1195-
// Since go 1.14 you can also use [*testing.T.Cleanup] method as in:
1196-
//
1197-
// func TestFetchArticles(t *testing.T) {
1198-
// httpmock.Activate()
1199-
// t.Cleanup(httpmock.Deactivate)
1204+
// Note that registered mocks and corresponding counters are not
1205+
// removed. The next time [Activate] will be called they will be
1206+
// active again. Use [DeactivateAndReset] to also remove registered
1207+
// mocks & counters.
12001208
//
1201-
// // when this test ends, the mock environment will close
1202-
// }
1209+
// It also restores all clients Transport field previously overridden
1210+
// by [ActivateNonDefault]. Unlike globally registered mocks, these
1211+
// clients won't be mocked anymore the next time [Activate] will be
1212+
// called.
12031213
//
1204-
// useful in test helpers to save your callers from calling defer themselves.
1214+
// See also [Reset] and [DeactivateAndReset].
12051215
func Deactivate() {
12061216
if Disabled() {
12071217
return
12081218
}
1209-
http.DefaultTransport = InitialTransport
1219+
1220+
if InitialTransport != nil {
1221+
http.DefaultTransport = InitialTransport
1222+
}
12101223

12111224
// reset the custom clients to use their original RoundTripper
12121225
oldClientsLock.Lock()
@@ -1219,24 +1232,72 @@ func Deactivate() {
12191232

12201233
// Reset removes any registered mocks and returns the mock
12211234
// environment to its initial state. It zeroes call counters too.
1235+
//
1236+
// See also [DeactivateAndReset].
12221237
func Reset() {
12231238
DefaultTransport.Reset()
12241239
}
12251240

1226-
// ZeroCallCounters zeroes call counters without touching registered responders.
1227-
func ZeroCallCounters() {
1228-
DefaultTransport.ZeroCallCounters()
1229-
}
1230-
12311241
// DeactivateAndReset is just a convenience method for calling
12321242
// [Deactivate] and then [Reset].
12331243
//
1234-
// Happy deferring!
1244+
// Often called at the end of the test like in:
1245+
//
1246+
// func TestFetchArticles(t *testing.T) {
1247+
// httpmock.Activate()
1248+
// t.Cleanup(httpmock.DeactivateAndReset)
1249+
//
1250+
// // when this test ends, the mock environment will close
1251+
// }
1252+
//
1253+
// you may prefer the simpler:
1254+
//
1255+
// func TestFetchArticles(t *testing.T) {
1256+
// httpmock.Activate(t)
1257+
//
1258+
// // when this test ends, the mock environment will close
1259+
// }
1260+
//
1261+
// See [Activate].
12351262
func DeactivateAndReset() {
12361263
Deactivate()
12371264
Reset()
12381265
}
12391266

1267+
// GetCallCountInfo gets the info on all the calls httpmock has caught
1268+
// since it was activated or reset. The info is returned as a map of
1269+
// the calling keys with the number of calls made to them as their
1270+
// value. The key is the method, a space, and the URL all concatenated
1271+
// together.
1272+
//
1273+
// As a special case, regexp responders generate 2 entries for each
1274+
// call. One for the call caught and the other for the rule that
1275+
// matched. For example:
1276+
//
1277+
// RegisterResponder("GET", `=~z\.com\z`, NewStringResponder(200, "body"))
1278+
// http.Get("http://z.com")
1279+
//
1280+
// will generate the following result:
1281+
//
1282+
// map[string]int{
1283+
// `GET http://z.com`: 1,
1284+
// `GET =~z\.com\z`: 1,
1285+
// }
1286+
func GetCallCountInfo() map[string]int {
1287+
return DefaultTransport.GetCallCountInfo()
1288+
}
1289+
1290+
// GetTotalCallCount gets the total number of calls httpmock has taken
1291+
// since it was first activated or last reset.
1292+
func GetTotalCallCount() int {
1293+
return DefaultTransport.GetTotalCallCount()
1294+
}
1295+
1296+
// ZeroCallCounters zeroes call counters without touching registered responders.
1297+
func ZeroCallCounters() {
1298+
DefaultTransport.ZeroCallCounters()
1299+
}
1300+
12401301
// RegisterMatcherResponder adds a new responder, associated with a given
12411302
// HTTP method, URL (or path) and [Matcher].
12421303
//
@@ -1310,8 +1371,8 @@ func DeactivateAndReset() {
13101371
//
13111372
// If method is a lower-cased version of CONNECT, DELETE, GET, HEAD,
13121373
// OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible
1313-
// mistake. This panic can be disabled by setting m.DontCheckMethod to
1314-
// true prior to this call.
1374+
// mistake. This panic can be disabled by setting
1375+
// [DefaultTransport].DontCheckMethod to true prior to this call.
13151376
//
13161377
// See also [RegisterResponder] if a matcher is not needed.
13171378
//
@@ -1422,8 +1483,8 @@ func RegisterResponder(method, url string, responder Responder) {
14221483
//
14231484
// If method is a lower-cased version of CONNECT, DELETE, GET, HEAD,
14241485
// OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible
1425-
// mistake. This panic can be disabled by setting m.DontCheckMethod to
1426-
// true prior to this call.
1486+
// mistake. This panic can be disabled by setting
1487+
// [DefaultTransport].DontCheckMethod to true prior to this call.
14271488
//
14281489
// See [RegisterRegexpResponder] if a matcher is not needed.
14291490
//
@@ -1458,7 +1519,7 @@ func RegisterRegexpMatcherResponder(method string, urlRegexp *regexp.Regexp, mat
14581519
// If method is a lower-cased version of CONNECT, DELETE, GET, HEAD,
14591520
// OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible
14601521
// mistake. This panic can be disabled by setting
1461-
// DefaultTransport.DontCheckMethod to true prior to this call.
1522+
// [DefaultTransport].DontCheckMethod to true prior to this call.
14621523
func RegisterRegexpResponder(method string, urlRegexp *regexp.Regexp, responder Responder) {
14631524
DefaultTransport.RegisterRegexpResponder(method, urlRegexp, responder)
14641525
}
@@ -1504,8 +1565,8 @@ func RegisterRegexpResponder(method string, urlRegexp *regexp.Regexp, responder
15041565
//
15051566
// If method is a lower-cased version of CONNECT, DELETE, GET, HEAD,
15061567
// OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible
1507-
// mistake. This panic can be disabled by setting m.DontCheckMethod to
1508-
// true prior to this call.
1568+
// mistake. This panic can be disabled by setting
1569+
// [DefaultTransport].DontCheckMethod to true prior to this call.
15091570
//
15101571
// See also [RegisterResponderWithQuery] if a matcher is not needed.
15111572
//
@@ -1592,7 +1653,7 @@ func RegisterMatcherResponderWithQuery(method, path string, query any, matcher M
15921653
// If method is a lower-cased version of CONNECT, DELETE, GET, HEAD,
15931654
// OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible
15941655
// mistake. This panic can be disabled by setting
1595-
// DefaultTransport.DontCheckMethod to true prior to this call.
1656+
// [DefaultTransport].DontCheckMethod to true prior to this call.
15961657
func RegisterResponderWithQuery(method, path string, query any, responder Responder) {
15971658
RegisterMatcherResponderWithQuery(method, path, query, Matcher{}, responder)
15981659
}

0 commit comments

Comments
 (0)