Skip to content

Commit ba1076d

Browse files
authored
Add .Unset method to mock (#982)
* Add .Off method to mock * Update README.md * Update mock.go * Update mock_test.go * Update README.md * Fix tests * Add unset test * remove prints * fix test * update readme
1 parent c31ea03 commit ba1076d

File tree

3 files changed

+143
-0
lines changed

3 files changed

+143
-0
lines changed

README.md

+25
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,31 @@ func TestSomethingWithPlaceholder(t *testing.T) {
190190

191191

192192
}
193+
194+
// TestSomethingElse2 is a third example that shows how you can use
195+
// the Unset method to cleanup handlers and then add new ones.
196+
func TestSomethingElse2(t *testing.T) {
197+
198+
// create an instance of our test object
199+
testObj := new(MyMockedObject)
200+
201+
// setup expectations with a placeholder in the argument list
202+
mockCall := testObj.On("DoSomething", mock.Anything).Return(true, nil)
203+
204+
// call the code we are testing
205+
targetFuncThatDoesSomethingWithObj(testObj)
206+
207+
// assert that the expectations were met
208+
testObj.AssertExpectations(t)
209+
210+
// remove the handler now so we can add another one that takes precedence
211+
mockCall.Unset()
212+
213+
// return false now instead of true
214+
testObj.On("DoSomething", mock.Anything).Return(false, nil)
215+
216+
testObj.AssertExpectations(t)
217+
}
193218
```
194219

195220
For more information on how to write mock code, check out the [API documentation for the `mock` package](http://godoc.org/github.com/stretchr/testify/mock).

mock/mock.go

+37
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,43 @@ func (c *Call) On(methodName string, arguments ...interface{}) *Call {
199199
return c.Parent.On(methodName, arguments...)
200200
}
201201

202+
// Unset removes a mock handler from being called.
203+
// test.On("func", mock.Anything).Unset()
204+
func (c *Call) Unset() *Call {
205+
var unlockOnce sync.Once
206+
207+
for _, arg := range c.Arguments {
208+
if v := reflect.ValueOf(arg); v.Kind() == reflect.Func {
209+
panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg))
210+
}
211+
}
212+
213+
c.lock()
214+
defer unlockOnce.Do(c.unlock)
215+
216+
foundMatchingCall := false
217+
218+
for i, call := range c.Parent.ExpectedCalls {
219+
if call.Method == c.Method {
220+
_, diffCount := call.Arguments.Diff(c.Arguments)
221+
if diffCount == 0 {
222+
foundMatchingCall = true
223+
// Remove from ExpectedCalls
224+
c.Parent.ExpectedCalls = append(c.Parent.ExpectedCalls[:i], c.Parent.ExpectedCalls[i+1:]...)
225+
}
226+
}
227+
}
228+
229+
if !foundMatchingCall {
230+
unlockOnce.Do(c.unlock)
231+
c.Parent.fail("\n\nmock: Could not find expected call\n-----------------------------\n\n%s\n\n",
232+
callString(c.Method, c.Arguments, true),
233+
)
234+
}
235+
236+
return c
237+
}
238+
202239
// Mock is the workhorse used to track activity on another object.
203240
// For an example of its usage, refer to the "Example Usage" section at the top
204241
// of this document.

mock/mock_test.go

+81
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,87 @@ func Test_Mock_On_WithFuncTypeArg(t *testing.T) {
462462
})
463463
}
464464

465+
func Test_Mock_Unset(t *testing.T) {
466+
// make a test impl object
467+
var mockedService = new(TestExampleImplementation)
468+
469+
call := mockedService.
470+
On("TheExampleMethodFuncType", "argA").
471+
Return("blah")
472+
473+
found, foundCall := mockedService.findExpectedCall("TheExampleMethodFuncType", "argA")
474+
require.NotEqual(t, -1, found)
475+
require.Equal(t, foundCall, call)
476+
477+
call.Unset()
478+
479+
found, foundCall = mockedService.findExpectedCall("TheExampleMethodFuncType", "argA")
480+
require.Equal(t, -1, found)
481+
482+
var expectedCall *Call
483+
require.Equal(t, expectedCall, foundCall)
484+
485+
fn := func(string) error { return nil }
486+
assert.Panics(t, func() {
487+
mockedService.TheExampleMethodFuncType(fn)
488+
})
489+
}
490+
491+
// Since every time you call On it creates a new object
492+
// the last time you call Unset it will only unset the last call
493+
func Test_Mock_Chained_UnsetOnlyUnsetsLastCall(t *testing.T) {
494+
// make a test impl object
495+
var mockedService = new(TestExampleImplementation)
496+
497+
// determine our current line number so we can assert the expected calls callerInfo properly
498+
_, _, line, _ := runtime.Caller(0)
499+
mockedService.
500+
On("TheExampleMethod1", 1, 1).
501+
Return(0).
502+
On("TheExampleMethod2", 2, 2).
503+
On("TheExampleMethod3", 3, 3, 3).
504+
Return(nil).
505+
Unset()
506+
507+
expectedCalls := []*Call{
508+
{
509+
Parent: &mockedService.Mock,
510+
Method: "TheExampleMethod1",
511+
Arguments: []interface{}{1, 1},
512+
ReturnArguments: []interface{}{0},
513+
callerInfo: []string{fmt.Sprintf("mock_test.go:%d", line+2)},
514+
},
515+
{
516+
Parent: &mockedService.Mock,
517+
Method: "TheExampleMethod2",
518+
Arguments: []interface{}{2, 2},
519+
ReturnArguments: []interface{}{},
520+
callerInfo: []string{fmt.Sprintf("mock_test.go:%d", line+4)},
521+
},
522+
}
523+
assert.Equal(t, 2, len(expectedCalls))
524+
assert.Equal(t, expectedCalls, mockedService.ExpectedCalls)
525+
}
526+
527+
func Test_Mock_UnsetIfAlreadyUnsetFails(t *testing.T) {
528+
// make a test impl object
529+
var mockedService = new(TestExampleImplementation)
530+
531+
mock1 := mockedService.
532+
On("TheExampleMethod1", 1, 1).
533+
Return(1)
534+
535+
assert.Equal(t, 1, len(mockedService.ExpectedCalls))
536+
mock1.Unset()
537+
assert.Equal(t, 0, len(mockedService.ExpectedCalls))
538+
539+
assert.Panics(t, func() {
540+
mock1.Unset()
541+
})
542+
543+
assert.Equal(t, 0, len(mockedService.ExpectedCalls))
544+
}
545+
465546
func Test_Mock_Return(t *testing.T) {
466547

467548
// make a test impl object

0 commit comments

Comments
 (0)