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
}
|