-
Notifications
You must be signed in to change notification settings - Fork 42
/
stub.go
276 lines (249 loc) · 8.7 KB
/
stub.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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
// Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package testing
import (
"fmt"
"reflect"
"sync"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
)
// StubCall records the name of a called function and the passed args.
type StubCall struct {
// Funcname is the name of the function that was called.
FuncName string
// Args is the set of arguments passed to the function. They are
// in the same order as the function's parameters
Args []interface{}
}
// Stub is used in testing to stand in for some other value, to record
// all calls to stubbed methods/functions, and to allow users to set the
// values that are returned from those calls. Stub is intended to be
// embedded in another struct that will define the methods to track:
//
// type stubConn struct {
// *testing.Stub
// Response []byte
// }
//
// func newStubConn() *stubConn {
// return &stubConn{
// Stub: &testing.Stub{},
// }
// }
//
// // Send implements Connection.
// func (fc *stubConn) Send(request string) []byte {
// fc.MethodCall(fc, "Send", request)
// return fc.Response, fc.NextErr()
// }
//
// As demonstrated in the example, embed a pointer to testing.Stub. This
// allows a single testing.Stub to be shared between multiple stubs.
//
// Error return values are set through Stub.Errors. Set it to the errors
// you want returned (or use the convenience method `SetErrors`). The
// `NextErr` method returns the errors from Stub.Errors in sequence,
// falling back to `DefaultError` when the sequence is exhausted. Thus
// each stubbed method should call `NextErr` to get its error return value.
//
// To validate calls made to the stub in a test call the CheckCalls
// (or CheckCall) method:
//
// s.stub.CheckCalls(c, []StubCall{{
// FuncName: "Send",
// Args: []interface{}{
// expected,
// },
// }})
//
// s.stub.CheckCall(c, 0, "Send", expected)
//
// Not only is Stub useful for building a interface implementation to
// use in testing (e.g. a network API client), it is also useful in
// regular function patching situations:
//
// type myStub struct {
// *testing.Stub
// }
//
// func (f *myStub) SomeFunc(arg interface{}) error {
// f.AddCall("SomeFunc", arg)
// return f.NextErr()
// }
//
// s.PatchValue(&somefunc, s.myStub.SomeFunc)
//
// This allows for easily monitoring the args passed to the patched
// func, as well as controlling the return value from the func in a
// clean manner (by simply setting the correct field on the stub).
type Stub struct {
mu sync.Mutex // serialises access the to following fields
// calls is the list of calls that have been registered on the stub
// (i.e. made on the stub's methods), in the order that they were
// made.
calls []StubCall
// receivers is the list of receivers for all the recorded calls.
// In the case of non-methods, the receiver is set to nil. The
// receivers are tracked here rather than as a Receiver field on
// StubCall because StubCall represents the common case for
// testing. Typically the receiver does not need to be checked.
receivers []interface{}
// errors holds the list of error return values to use for
// successive calls to methods that return an error. Each call
// pops the next error off the list. An empty list (the default)
// implies a nil error. nil may be precede actual errors in the
// list, which means that the first calls will succeed, followed
// by the failure. All this is facilitated through the Err method.
errors []error
}
// TODO(ericsnow) Add something similar to NextErr for all return values
// using reflection?
// NextErr returns the error that should be returned on the nth call to
// any method on the stub. It should be called for the error return in
// all stubbed methods.
func (f *Stub) NextErr() error {
f.mu.Lock()
defer f.mu.Unlock()
if len(f.errors) == 0 {
return nil
}
err := f.errors[0]
f.errors = f.errors[1:]
return err
}
// PopNoErr pops off the next error without returning it. If the error
// is not nil then PopNoErr will panic.
//
// PopNoErr is useful in stub methods that do not return an error.
func (f *Stub) PopNoErr() {
if err := f.NextErr(); err != nil {
panic(fmt.Sprintf("expected a nil error, got %v", err))
}
}
func (f *Stub) addCall(rcvr interface{}, funcName string, args []interface{}) {
f.mu.Lock()
defer f.mu.Unlock()
f.calls = append(f.calls, StubCall{
FuncName: funcName,
Args: args,
})
f.receivers = append(f.receivers, rcvr)
}
// Calls returns the list of calls that have been registered on the stub
// (i.e. made on the stub's methods), in the order that they were made.
func (f *Stub) Calls() []StubCall {
f.mu.Lock()
defer f.mu.Unlock()
v := make([]StubCall, len(f.calls))
copy(v, f.calls)
return v
}
// ResetCalls erases the calls recorded by this Stub.
func (f *Stub) ResetCalls() {
f.mu.Lock()
defer f.mu.Unlock()
f.calls = nil
}
// AddCall records a stubbed function call for later inspection using the
// CheckCalls method. A nil receiver is recorded. Thus for methods use
// MethodCall. All stubbed functions should call AddCall.
func (f *Stub) AddCall(funcName string, args ...interface{}) {
f.addCall(nil, funcName, args)
}
// MethodCall records a stubbed method call for later inspection using
// the CheckCalls method. The receiver is added to Stub.Receivers.
func (f *Stub) MethodCall(receiver interface{}, funcName string, args ...interface{}) {
f.addCall(receiver, funcName, args)
}
// SetErrors sets the sequence of error returns for the stub. Each call
// to Err (thus each stub method call) pops an error off the front. So
// frontloading nil here will allow calls to pass, followed by a
// failure.
func (f *Stub) SetErrors(errors ...error) {
f.mu.Lock()
defer f.mu.Unlock()
f.errors = errors
}
// CheckCalls verifies that the history of calls on the stub's methods
// matches the expected calls. The receivers are not checked. If they
// are significant then check Stub.Receivers separately.
func (f *Stub) CheckCalls(c *gc.C, expected []StubCall) {
if !f.CheckCallNames(c, stubCallNames(expected...)...) {
return
}
c.Check(f.calls, jc.DeepEquals, expected)
}
// CheckCallsUnordered verifies that the history of calls on the stub's methods
// contains the expected calls. The receivers are not checked. If they
// are significant then check Stub.Receivers separately.
// This method explicitly does not check if the calls were made in order, just
// whether they have been made.
func (f *Stub) CheckCallsUnordered(c *gc.C, expected []StubCall) {
// Take a copy of all calls made to the stub.
calls := f.calls[:]
checkCallMade := func(call StubCall) {
for i, madeCall := range calls {
if reflect.DeepEqual(call, madeCall) {
// Remove found call from the copy of all-calls-made collection.
calls = append(calls[:i], calls[i+1:]...)
break
}
}
}
for _, call := range expected {
checkCallMade(call)
}
// If all expected calls were made, our resulting collection should be empty.
c.Check(calls, gc.DeepEquals, []StubCall{})
}
// CheckCall checks the recorded call at the given index against the
// provided values. If the index is out of bounds then the check fails.
// The receiver is not checked. If it is significant for a test then it
// can be checked separately:
//
// c.Check(mystub.Receivers[index], gc.Equals, expected)
func (f *Stub) CheckCall(c *gc.C, index int, funcName string, args ...interface{}) {
f.mu.Lock()
defer f.mu.Unlock()
if !c.Check(index, jc.LessThan, len(f.calls)) {
return
}
call := f.calls[index]
expected := StubCall{
FuncName: funcName,
Args: args,
}
c.Check(call, jc.DeepEquals, expected)
}
// CheckCallNames verifies that the in-order list of called method names
// matches the expected calls.
func (f *Stub) CheckCallNames(c *gc.C, expected ...string) bool {
f.mu.Lock()
defer f.mu.Unlock()
funcNames := stubCallNames(f.calls...)
return c.Check(funcNames, jc.DeepEquals, expected)
}
// CheckNoCalls verifies that none of the stub's methods have been called.
func (f *Stub) CheckNoCalls(c *gc.C) {
f.CheckCalls(c, nil)
}
// CheckErrors verifies that the list of errors is matches the expected list.
func (f *Stub) CheckErrors(c *gc.C, expected ...error) bool {
f.mu.Lock()
defer f.mu.Unlock()
return c.Check(f.errors, jc.DeepEquals, expected)
}
// CheckReceivers verifies that the list of errors is matches the expected list.
func (f *Stub) CheckReceivers(c *gc.C, expected ...interface{}) bool {
f.mu.Lock()
defer f.mu.Unlock()
return c.Check(f.receivers, jc.DeepEquals, expected)
}
func stubCallNames(calls ...StubCall) []string {
var funcNames []string
for _, call := range calls {
funcNames = append(funcNames, call.FuncName)
}
return funcNames
}