Repository: stretchr/testify
Pull request #886 (2020-02-14 06:14:27)

Check method callable
Added function 'IsMethodCallable', which checking that the method can be called.

### Example
```
if !userService.IsMethodCallable(t, "FindByID", userID) {
    return nil
}
args := s.MethodCalled(t, "FindByUserID", userID)
// TODO
```

glesica: Agree with the comments by @nelsam 

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e155426..a3a062c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -42,7 +42,7 @@ See `MAINTAINERS.md` for a list of users who can approve / merge your changes.
 ## Issues
 
 If you find a bug or think of a useful feature you'd like to see added to Testify, the best thing
-you can do is make the necessary changes and open a pull requests (see above). If that isn't an
+you can do is make the necessary changes and open a pull request (see above). If that isn't an
 option, or if you'd like to discuss your change before you write the code, open an issue!
 
 Please provide enough context in the issue description that other members of the community can
diff --git a/mock/mock.go b/mock/mock.go
index 6b80d3c..6be8937 100644
--- a/mock/mock.go
+++ b/mock/mock.go
@@ -147,7 +147,7 @@ func (c *Call) After(d time.Duration) *Call {
 }
 
 // Run sets a handler to be called before returning. It can be used when
-// mocking a method such as unmarshalers that takes a pointer to a struct and
+// mocking a method (such as an unmarshaler) that takes a pointer to a struct and
 // sets properties in such struct
 //
 //    Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}").Return().Run(func(args Arguments) {
@@ -527,6 +527,45 @@ func (m *Mock) AssertNotCalled(t TestingT, methodName string, arguments ...inter
 	return true
 }
 
+// IsMethodCallable checking that the method can be called
+// If the method was called more than `Repeatability` return false
+func (m *Mock) IsMethodCallable(t TestingT, methodName string, arguments ...interface{}) bool {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+
+	for _, v := range m.ExpectedCalls {
+		if v.Method != methodName {
+			continue
+		}
+		if len(arguments) != len(v.Arguments) {
+			continue
+		}
+		if v.Repeatability < v.totalCalls {
+			continue
+		}
+		if isArgsEqual(v.Arguments, arguments) {
+			return true
+		}
+	}
+	return false
+}
+
+// isArgsEqual compares arguments
+func isArgsEqual(expected Arguments, args []interface{}) bool {
+	if len(expected) != len(args) {
+		return false
+	}
+	for i, v := range args {
+		if !reflect.DeepEqual(expected[i], v) {
+			return false
+		}
+	}
+	return true
+}
+
 func (m *Mock) methodWasCalled(methodName string, expected []interface{}) bool {
 	for _, call := range m.calls() {
 		if call.Method == methodName {
diff --git a/suite/interfaces.go b/suite/interfaces.go
index b55db43..b37cb04 100644
--- a/suite/interfaces.go
+++ b/suite/interfaces.go
@@ -1,10 +1,12 @@
 package suite
 
+import "testing"
+
 // TestingSuite can store and return the current *testing.T context
 // generated by 'go test'.
 type TestingSuite interface {
-	T() TestingT
-	SetT(TestingT)
+	T() *testing.T
+	SetT(*testing.T)
 }
 
 // SetupAllSuite has a SetupSuite method, which will run before the
diff --git a/suite/suite.go b/suite/suite.go
index 6eaaed1..6195301 100644
--- a/suite/suite.go
+++ b/suite/suite.go
@@ -17,30 +17,21 @@ import (
 var allTestsFilter = func(_, _ string) (bool, error) { return true, nil }
 var matchMethod = flag.String("testify.m", "", "regular expression to select tests of the testify suite to run")
 
-type TestingT interface {
-	Run(name string, f func(t *testing.T)) bool
-	Errorf(format string, args ...interface{})
-	FailNow()
-	Log(args ...interface{})
-	Logf(format string, args ...interface{})
-	Skip(args ...interface{})
-}
-
 // Suite is a basic testing suite with methods for storing and
 // retrieving the current *testing.T context.
 type Suite struct {
 	*assert.Assertions
 	require *require.Assertions
-	t       TestingT
+	t       *testing.T
 }
 
 // T retrieves the current *testing.T context.
-func (suite *Suite) T() TestingT {
+func (suite *Suite) T() *testing.T {
 	return suite.t
 }
 
 // SetT sets the current *testing.T context.
-func (suite *Suite) SetT(t TestingT) {
+func (suite *Suite) SetT(t *testing.T) {
 	suite.t = t
 	suite.Assertions = assert.New(t)
 	suite.require = require.New(t)


Test patch:
diff --git a/mock/mock_test.go b/mock/mock_test.go
index 7da34b7..bfa123f 100644
--- a/mock/mock_test.go
+++ b/mock/mock_test.go
@@ -1143,6 +1143,41 @@ func Test_Mock_AssertNotCalled(t *testing.T) {
 
 }
 
+func Test_Mock_IsMethodCallable(t *testing.T) {
+	var mockedService = new(TestExampleImplementation)
+
+	arg := []Call{{Repeatability: 1}, {Repeatability: 2}}
+	arg2 := []Call{{Repeatability: 1}, {Repeatability: 1}}
+	arg3 := []Call{{Repeatability: 1}, {Repeatability: 1}}
+
+	mockedService.On("Test_Mock_IsMethodCallable", arg2).Return(true).Twice()
+
+	assert.False(t, mockedService.IsMethodCallable(t, "Test_Mock_IsMethodCallable", arg))
+	assert.True(t, mockedService.IsMethodCallable(t, "Test_Mock_IsMethodCallable", arg2))
+	assert.True(t, mockedService.IsMethodCallable(t, "Test_Mock_IsMethodCallable", arg3))
+
+	mockedService.MethodCalled("Test_Mock_IsMethodCallable", arg2)
+	mockedService.MethodCalled("Test_Mock_IsMethodCallable", arg2)
+
+	assert.False(t, mockedService.IsMethodCallable(t, "Test_Mock_IsMethodCallable", arg2))
+}
+
+func TestIsArgsEqual(t *testing.T) {
+	var expected = Arguments{5, 3, 4, 6, 7, 2}
+	var args = make([]interface{}, 5)
+	for i := 1; i < len(expected); i++ {
+		args[i-1] = expected[i]
+	}
+	args[2] = expected[1]
+	assert.False(t, isArgsEqual(expected, args))
+
+	var arr = make([]interface{}, 6)
+	for i := 0; i < len(expected); i++ {
+		arr[i] = expected[i]
+	}
+	assert.True(t, isArgsEqual(expected, arr))
+}
+
 func Test_Mock_AssertOptional(t *testing.T) {
 	// Optional called
 	var ms1 = new(TestExampleImplementation)
diff --git a/suite/suite_test.go b/suite/suite_test.go
index a178600..5d757dd 100644
--- a/suite/suite_test.go
+++ b/suite/suite_test.go
@@ -10,7 +10,6 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/mock"
 	"github.com/stretchr/testify/require"
 )
 
@@ -483,27 +482,3 @@ func (s *CallOrderSuite) Test_A() {
 func (s *CallOrderSuite) Test_B() {
 	s.call("Test B")
 }
-
-func TestMockTCompatibility(t *testing.T) {
-	suiteTester := new(SuiteTester)
-	suiteTester.SetT(t)
-
-	// compatible with mock.T
-	_, ok := suiteTester.T().(mock.TestingT)
-	assert.True(t, ok)
-
-	// compatible with testing.T
-	_, ok = suiteTester.T().(*testing.T)
-	assert.True(t, ok)
-
-	// compatible with testing.TB
-	_, ok = suiteTester.T().(testing.TB)
-	assert.True(t, ok)
-
-	// control check
-	type wrongInterface interface {
-		NotInSuiteT() string
-	}
-	_, ok = suiteTester.T().(wrongInterface)
-	assert.False(t, ok)
-}