From 282fbccdb975b5e8490c5dbd3dae91a27bb3873c Mon Sep 17 00:00:00 2001 From: Paul Dufour Date: Wed, 22 Jul 2020 14:13:09 -0700 Subject: [PATCH 01/10] Add .Off method to mock --- README.md | 25 +++++++++++++++++++++ mock/mock.go | 36 +++++++++++++++++++++++++++++++ mock/mock_test.go | 55 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) diff --git a/README.md b/README.md index 62f145e24..aea56fa5b 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,31 @@ func TestSomethingElse(t *testing.T) { } + +// TestSomethingElse2 is a third example that shows how you can use +// the Off method to cleanup handlers and then add new ones. +func TestSomethingElse2(t *testing.T) { + + // create an instance of our test object + testObj := new(MyMockedObject) + + // setup expectations with a placeholder in the argument list + testObj.On("DoSomething", mock.Anything).Return(true, nil) + + // call the code we are testing + targetFuncThatDoesSomethingWithObj(testObj) + + // assert that the expectations were met + testObj.AssertExpectations(t) + + // remove the handler now so we can add another one that takes precedence + testObj.Off("DoSomething", mock.Anything) + + // return false now instead of true + testObj.On("DoSomething", mock.Anything).Return(false, nil) + + testObj.AssertExpectations(t) +} ``` 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). diff --git a/mock/mock.go b/mock/mock.go index c6df4485a..e960a215f 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -275,6 +275,42 @@ func (m *Mock) On(methodName string, arguments ...interface{}) *Call { return c } +// Off removes a mock handler from being called. You must pass the same exact +// arguments that were called in the original .On call. +// +// Mock.Off("MyMethod", arg1, arg2) +func (m *Mock) Off(methodName string, arguments ...interface{}) *Mock { + for _, arg := range arguments { + if v := reflect.ValueOf(arg); v.Kind() == reflect.Func { + panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg)) + } + } + + m.mutex.Lock() + + foundMatchingCall := false + + for i, call := range m.ExpectedCalls { + if call.Method == methodName { + _, diffCount := call.Arguments.Diff(arguments) + if diffCount == 0 { + foundMatchingCall = true + // Remove from ExpectedCalls + m.ExpectedCalls = append(m.ExpectedCalls[:i], m.ExpectedCalls[i+1:]...) + } + } + } + + m.mutex.Unlock() + if !foundMatchingCall { + m.fail("\n\nmock: Could not find expected call\n-----------------------------\n\n%s\n\n", + callString(methodName, arguments, true), + ) + } + + return m +} + // /* // Recording and responding to activity // */ diff --git a/mock/mock_test.go b/mock/mock_test.go index cb4954145..80e7542f1 100644 --- a/mock/mock_test.go +++ b/mock/mock_test.go @@ -462,6 +462,61 @@ func Test_Mock_On_WithFuncTypeArg(t *testing.T) { }) } +func Test_Mock_Off(t *testing.T) { + // make a test impl object + var mockedService = new(TestExampleImplementation) + + call := mockedService. + On("TheExampleMethodFuncType", "argA"). + Return(nil) + + found, foundCall := mockedService.findExpectedCall("TheExampleMethodFuncType", "argA") + require.NotEqual(t, -1, found) + require.Equal(t, foundCall, call) + + mockedService. + Off("TheExampleMethodFuncType", "argA") + + found, foundCall = mockedService.findExpectedCall("TheExampleMethodFuncType", "argA") + require.Equal(t, -1, found) + + var expectedCall *Call + require.Equal(t, expectedCall, foundCall) + + fn := func(string) error { return nil } + assert.Panics(t, func() { + mockedService.TheExampleMethodFuncType(fn) + }) +} + +func Test_Mock_Chained_Off(t *testing.T) { + // make a test impl object + var mockedService = new(TestExampleImplementation) + + // determine our current line number so we can assert the expected calls callerInfo properly + _, _, line, _ := runtime.Caller(0) + mockedService. + On("TheExampleMethod1", 1, 1). + Return(0). + On("TheExampleMethod2", 2, 2). + On("TheExampleMethod3", 3, 3, 3). + Return(nil) + + mockedService. + Off("TheExampleMethod2", 2, 2). + Off("TheExampleMethod3", 3, 3, 3) + + expectedCalls := []*Call{ + { + Parent: &mockedService.Mock, + Method: "TheExampleMethod1", + Arguments: []interface{}{1, 1}, + ReturnArguments: []interface{}{0}, + callerInfo: []string{fmt.Sprintf("mock_test.go:%d", line+2)}, + }, + } + assert.Equal(t, expectedCalls, mockedService.ExpectedCalls) +} func Test_Mock_Return(t *testing.T) { // make a test impl object From 2701b3c6627e04378079705eb2ae8e3a3faad39e Mon Sep 17 00:00:00 2001 From: Paul Dufour Date: Wed, 22 Jun 2022 01:39:24 +0100 Subject: [PATCH 02/10] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b6c0632a..992a42108 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ func TestSomethingElse2(t *testing.T) { testObj.AssertExpectations(t) // remove the handler now so we can add another one that takes precedence - testObj.Off("DoSomething", mock.Anything) + testObj.Unset("DoSomething", mock.Anything) // return false now instead of true testObj.On("DoSomething", mock.Anything).Return(false, nil) From 00d3e27111e8afa07b40a880e11c98d8b1744541 Mon Sep 17 00:00:00 2001 From: Paul Dufour Date: Wed, 22 Jun 2022 01:39:52 +0100 Subject: [PATCH 03/10] Update mock.go --- mock/mock.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mock/mock.go b/mock/mock.go index c655faf3e..c9f64defb 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -289,11 +289,11 @@ func (m *Mock) On(methodName string, arguments ...interface{}) *Call { return c } -// Off removes a mock handler from being called. You must pass the same exact +// Unset removes a mock handler from being called. You must pass the same exact // arguments that were called in the original .On call. // -// Mock.Off("MyMethod", arg1, arg2) -func (m *Mock) Off(methodName string, arguments ...interface{}) *Mock { +// Mock.Unset("MyMethod", arg1, arg2) +func (m *Mock) Unset(methodName string, arguments ...interface{}) *Mock { for _, arg := range arguments { if v := reflect.ValueOf(arg); v.Kind() == reflect.Func { panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg)) From d921f420ce67f7e31821e6009dece3374541c376 Mon Sep 17 00:00:00 2001 From: Paul Dufour Date: Wed, 22 Jun 2022 01:40:26 +0100 Subject: [PATCH 04/10] Update mock_test.go --- mock/mock_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mock/mock_test.go b/mock/mock_test.go index b5063e195..fc5b5258c 100644 --- a/mock/mock_test.go +++ b/mock/mock_test.go @@ -462,7 +462,7 @@ func Test_Mock_On_WithFuncTypeArg(t *testing.T) { }) } -func Test_Mock_Off(t *testing.T) { +func Test_Mock_Unset(t *testing.T) { // make a test impl object var mockedService = new(TestExampleImplementation) @@ -475,7 +475,7 @@ func Test_Mock_Off(t *testing.T) { require.Equal(t, foundCall, call) mockedService. - Off("TheExampleMethodFuncType", "argA") + Unset("TheExampleMethodFuncType", "argA") found, foundCall = mockedService.findExpectedCall("TheExampleMethodFuncType", "argA") require.Equal(t, -1, found) @@ -489,7 +489,7 @@ func Test_Mock_Off(t *testing.T) { }) } -func Test_Mock_Chained_Off(t *testing.T) { +func Test_Mock_Chained_Unset(t *testing.T) { // make a test impl object var mockedService = new(TestExampleImplementation) @@ -503,8 +503,8 @@ func Test_Mock_Chained_Off(t *testing.T) { Return(nil) mockedService. - Off("TheExampleMethod2", 2, 2). - Off("TheExampleMethod3", 3, 3, 3) + Unset("TheExampleMethod2", 2, 2). + Unset("TheExampleMethod3", 3, 3, 3) expectedCalls := []*Call{ { From c020db96ccac7260410944407f04d53c93ebe770 Mon Sep 17 00:00:00 2001 From: Paul Dufour Date: Wed, 22 Jun 2022 01:41:20 +0100 Subject: [PATCH 05/10] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 992a42108..536c5b046 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,7 @@ func TestSomethingWithPlaceholder(t *testing.T) { } // TestSomethingElse2 is a third example that shows how you can use -// the Off method to cleanup handlers and then add new ones. +// the Unset method to cleanup handlers and then add new ones. func TestSomethingElse2(t *testing.T) { // create an instance of our test object From 3ca34d998b448a4cfeef174c27f2f039dbfd4386 Mon Sep 17 00:00:00 2001 From: Paul Dufour Date: Wed, 22 Jun 2022 02:10:16 +0100 Subject: [PATCH 06/10] Fix tests --- mock/mock.go | 72 +++++++++++++++++++++++------------------------ mock/mock_test.go | 25 ++++++++++------ 2 files changed, 52 insertions(+), 45 deletions(-) diff --git a/mock/mock.go b/mock/mock.go index c9f64defb..aec45f7cb 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -199,6 +199,42 @@ func (c *Call) On(methodName string, arguments ...interface{}) *Call { return c.Parent.On(methodName, arguments...) } +// Unset removes a mock handler from being called. +// test.On("func", mock.Anything).Unset() +func (c *Call) Unset() *Call { + for _, arg := range c.Arguments { + if v := reflect.ValueOf(arg); v.Kind() == reflect.Func { + panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg)) + } + } + + c.lock() + c.Parent.mutex.Lock() + + foundMatchingCall := false + + for i, call := range c.Parent.ExpectedCalls { + if call.Method == c.Method { + _, diffCount := call.Arguments.Diff(c.Arguments) + if diffCount == 0 { + foundMatchingCall = true + // Remove from ExpectedCalls + c.Parent.ExpectedCalls = append(c.Parent.ExpectedCalls[:i], c.Parent.ExpectedCalls[i+1:]...) + } + } + } + + c.Parent.mutex.Unlock() + c.unlock() + if !foundMatchingCall { + c.Parent.fail("\n\nmock: Could not find expected call\n-----------------------------\n\n%s\n\n", + callString(c.Method, c.Arguments, true), + ) + } + + return c +} + // Mock is the workhorse used to track activity on another object. // For an example of its usage, refer to the "Example Usage" section at the top // of this document. @@ -289,42 +325,6 @@ func (m *Mock) On(methodName string, arguments ...interface{}) *Call { return c } -// Unset removes a mock handler from being called. You must pass the same exact -// arguments that were called in the original .On call. -// -// Mock.Unset("MyMethod", arg1, arg2) -func (m *Mock) Unset(methodName string, arguments ...interface{}) *Mock { - for _, arg := range arguments { - if v := reflect.ValueOf(arg); v.Kind() == reflect.Func { - panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg)) - } - } - - m.mutex.Lock() - - foundMatchingCall := false - - for i, call := range m.ExpectedCalls { - if call.Method == methodName { - _, diffCount := call.Arguments.Diff(arguments) - if diffCount == 0 { - foundMatchingCall = true - // Remove from ExpectedCalls - m.ExpectedCalls = append(m.ExpectedCalls[:i], m.ExpectedCalls[i+1:]...) - } - } - } - - m.mutex.Unlock() - if !foundMatchingCall { - m.fail("\n\nmock: Could not find expected call\n-----------------------------\n\n%s\n\n", - callString(methodName, arguments, true), - ) - } - - return m -} - // /* // Recording and responding to activity // */ diff --git a/mock/mock_test.go b/mock/mock_test.go index fc5b5258c..70da64449 100644 --- a/mock/mock_test.go +++ b/mock/mock_test.go @@ -468,14 +468,13 @@ func Test_Mock_Unset(t *testing.T) { call := mockedService. On("TheExampleMethodFuncType", "argA"). - Return(nil) + Return("blah") found, foundCall := mockedService.findExpectedCall("TheExampleMethodFuncType", "argA") require.NotEqual(t, -1, found) require.Equal(t, foundCall, call) - mockedService. - Unset("TheExampleMethodFuncType", "argA") + call.Unset() found, foundCall = mockedService.findExpectedCall("TheExampleMethodFuncType", "argA") require.Equal(t, -1, found) @@ -489,7 +488,9 @@ func Test_Mock_Unset(t *testing.T) { }) } -func Test_Mock_Chained_Unset(t *testing.T) { +// Since every time you call On it creates a new object +// the last time you call Unset it will only unset the last call +func Test_Mock_Chained_UnsetOnlyUnsetsLastCall(t *testing.T) { // make a test impl object var mockedService = new(TestExampleImplementation) @@ -500,11 +501,8 @@ func Test_Mock_Chained_Unset(t *testing.T) { Return(0). On("TheExampleMethod2", 2, 2). On("TheExampleMethod3", 3, 3, 3). - Return(nil) - - mockedService. - Unset("TheExampleMethod2", 2, 2). - Unset("TheExampleMethod3", 3, 3, 3) + Return(nil). + Unset() expectedCalls := []*Call{ { @@ -514,9 +512,18 @@ func Test_Mock_Chained_Unset(t *testing.T) { ReturnArguments: []interface{}{0}, callerInfo: []string{fmt.Sprintf("mock_test.go:%d", line+2)}, }, + { + Parent: &mockedService.Mock, + Method: "TheExampleMethod2", + Arguments: []interface{}{2, 2}, + ReturnArguments: []interface{}{0}, + callerInfo: []string{fmt.Sprintf("mock_test.go:%d", line+4)}, + }, } + assert.Equal(t, 2, len(expectedCalls)) assert.Equal(t, expectedCalls, mockedService.ExpectedCalls) } + func Test_Mock_Return(t *testing.T) { // make a test impl object From c66b913594eba9af6604d224948add818efb9490 Mon Sep 17 00:00:00 2001 From: Paul Dufour Date: Wed, 22 Jun 2022 02:55:34 +0100 Subject: [PATCH 07/10] Add unset test --- mock/mock.go | 9 ++++++--- mock/mock_test.go | 27 ++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/mock/mock.go b/mock/mock.go index aec45f7cb..d2f7f1249 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -202,6 +202,8 @@ func (c *Call) On(methodName string, arguments ...interface{}) *Call { // Unset removes a mock handler from being called. // test.On("func", mock.Anything).Unset() func (c *Call) Unset() *Call { + var unlockOnce sync.Once + for _, arg := range c.Arguments { if v := reflect.ValueOf(arg); v.Kind() == reflect.Func { panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg)) @@ -209,7 +211,7 @@ func (c *Call) Unset() *Call { } c.lock() - c.Parent.mutex.Lock() + defer unlockOnce.Do(c.unlock) foundMatchingCall := false @@ -219,14 +221,14 @@ func (c *Call) Unset() *Call { if diffCount == 0 { foundMatchingCall = true // Remove from ExpectedCalls + fmt.Println("expectedCalls unset") c.Parent.ExpectedCalls = append(c.Parent.ExpectedCalls[:i], c.Parent.ExpectedCalls[i+1:]...) } } } - c.Parent.mutex.Unlock() - c.unlock() if !foundMatchingCall { + unlockOnce.Do(c.unlock) c.Parent.fail("\n\nmock: Could not find expected call\n-----------------------------\n\n%s\n\n", callString(c.Method, c.Arguments, true), ) @@ -294,6 +296,7 @@ func (m *Mock) Test(t TestingT) { // In case that a test was defined, it uses the test APIs for failing a test, // otherwise it uses panic. func (m *Mock) fail(format string, args ...interface{}) { + fmt.Println("hello") m.mutex.Lock() defer m.mutex.Unlock() diff --git a/mock/mock_test.go b/mock/mock_test.go index 70da64449..93a61f17c 100644 --- a/mock/mock_test.go +++ b/mock/mock_test.go @@ -516,7 +516,7 @@ func Test_Mock_Chained_UnsetOnlyUnsetsLastCall(t *testing.T) { Parent: &mockedService.Mock, Method: "TheExampleMethod2", Arguments: []interface{}{2, 2}, - ReturnArguments: []interface{}{0}, + ReturnArguments: []interface{}{}, callerInfo: []string{fmt.Sprintf("mock_test.go:%d", line+4)}, }, } @@ -524,6 +524,31 @@ func Test_Mock_Chained_UnsetOnlyUnsetsLastCall(t *testing.T) { assert.Equal(t, expectedCalls, mockedService.ExpectedCalls) } +func Test_Mock_UnsetIfAlreadyUnsetFails(t *testing.T) { + // make a test impl object + var mockedService = new(TestExampleImplementation) + + mock1 := mockedService. + On("TheExampleMethod1", 1, 1). + Return(1) + + assert.Equal(t, 1, mockedService.ExpectedCalls) + mock1.Unset() + assert.Equal(t, 0, len(mockedService.ExpectedCalls)) + + defer func() { + if r := recover(); r != nil { + matchingExp := regexp.MustCompile( + `.*Could not find expected call.*`) + assert.Regexp(t, matchingExp, r) + } + }() + + mock1.Unset() + + assert.Equal(t, 0, len(mockedService.ExpectedCalls)) +} + func Test_Mock_Return(t *testing.T) { // make a test impl object From a552fd9c175413277a17d4cee1dda250e24f82f0 Mon Sep 17 00:00:00 2001 From: Paul Dufour Date: Wed, 22 Jun 2022 02:58:11 +0100 Subject: [PATCH 08/10] remove prints --- mock/mock.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/mock/mock.go b/mock/mock.go index d2f7f1249..769aed8b3 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -221,7 +221,6 @@ func (c *Call) Unset() *Call { if diffCount == 0 { foundMatchingCall = true // Remove from ExpectedCalls - fmt.Println("expectedCalls unset") c.Parent.ExpectedCalls = append(c.Parent.ExpectedCalls[:i], c.Parent.ExpectedCalls[i+1:]...) } } @@ -296,7 +295,6 @@ func (m *Mock) Test(t TestingT) { // In case that a test was defined, it uses the test APIs for failing a test, // otherwise it uses panic. func (m *Mock) fail(format string, args ...interface{}) { - fmt.Println("hello") m.mutex.Lock() defer m.mutex.Unlock() From 422fe49b9b07394bf0f27d8468e74159ba07519d Mon Sep 17 00:00:00 2001 From: Paul Dufour Date: Wed, 22 Jun 2022 03:03:28 +0100 Subject: [PATCH 09/10] fix test --- mock/mock_test.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/mock/mock_test.go b/mock/mock_test.go index 93a61f17c..211568690 100644 --- a/mock/mock_test.go +++ b/mock/mock_test.go @@ -532,19 +532,13 @@ func Test_Mock_UnsetIfAlreadyUnsetFails(t *testing.T) { On("TheExampleMethod1", 1, 1). Return(1) - assert.Equal(t, 1, mockedService.ExpectedCalls) + assert.Equal(t, 1, len(mockedService.ExpectedCalls)) mock1.Unset() assert.Equal(t, 0, len(mockedService.ExpectedCalls)) - defer func() { - if r := recover(); r != nil { - matchingExp := regexp.MustCompile( - `.*Could not find expected call.*`) - assert.Regexp(t, matchingExp, r) - } - }() - - mock1.Unset() + assert.Panics(t, func() { + mock1.Unset() + }) assert.Equal(t, 0, len(mockedService.ExpectedCalls)) } From 807fc1b40e6fa0b5e00feb059852fc74b7306227 Mon Sep 17 00:00:00 2001 From: Paul Dufour Date: Wed, 22 Jun 2022 03:11:17 +0100 Subject: [PATCH 10/10] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 536c5b046..ce6d3de28 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ func TestSomethingElse2(t *testing.T) { testObj := new(MyMockedObject) // setup expectations with a placeholder in the argument list - testObj.On("DoSomething", mock.Anything).Return(true, nil) + mockCall := testObj.On("DoSomething", mock.Anything).Return(true, nil) // call the code we are testing targetFuncThatDoesSomethingWithObj(testObj) @@ -208,7 +208,7 @@ func TestSomethingElse2(t *testing.T) { testObj.AssertExpectations(t) // remove the handler now so we can add another one that takes precedence - testObj.Unset("DoSomething", mock.Anything) + mockCall.Unset() // return false now instead of true testObj.On("DoSomething", mock.Anything).Return(false, nil)