corbym / gocrest

GoCrest - Hamcrest-like matchers for Go
BSD 3-Clause "New" or "Revised" License
105 stars 7 forks source link

Implement Testify custom matchers via mock.MatchedBy #6

Open StevenACoffman opened 3 years ago

StevenACoffman commented 3 years ago

Hi, I saw the recent gomock discussion and would love documentation on how to Implement Testify's custom matchers via mock.MatchedBy using this library.

// Anything and AnythingOfType matchers
mockDoer.
    On("Do", mock.Anything, mock.AnythingOfType("string"))

// Custom matcher
mockDoer.
    On("Do", 1, mock.MatchedBy(func(x string) bool {
        return strings.HasPrefix(x, "abc")
    })
corbym commented 3 years ago

Although I haven't used the testify library, I do not think we could use gocrest matchers out of the box for the mock.MatchedBy method - the testify code would probably need an interface ala gomock that matches the func Matches(x interface{}) bool matcher method of the gocrest struct.

e.g.

package mock

type Matcher interface {
  // Matches returns whether x is a match.
  Matches(x interface{}) bool
}

The mock.MatchedBy would then need to use the interface instead of the empty interface:

func MatchedBy(fn interface{}) argumentMatcher

would become

func MatchedBy(fn mock.Matcher) argumentMatcher

With that you can then write:

mockDoer.
    On("Do", 1, mock.MatchedBy(has.Prefix("abc"))

... but until mock.MatchedBy changes it's interface, this isn't something we could support alone.

The other option is to make the argumentMatcher an interface instead of a struct:

// argumentMatcher performs custom argument matching, returning whether or
// not the argument is matched by the expectation fixture function.
type argumentMatcher struct {
    // fn is a function which accepts one argument, and returns a bool.
    fn reflect.Value
}

so you could write something like:

mockDoer.
    On("Do", 1, has.Prefix("abc"))

-- not sure that would work though as I haven't tested it.

jcyamacho commented 1 year ago

At least would be nice to have the public Matcher interface from @corbym example instead of argumentMatcher type. This way we can create custom matchers to reuse.

if MatchedBy becomes like:

func MatchedBy(fn interface{}) Matcher

and internally in the Argumnts.Diff function this code:

if matcher, ok := expected.(argumentMatcher); ok {
    .....
}

becomes more like:

if matcher, ok := expected.(Matcher); ok {
    .....
}

then we can create custom matchers implementing the Matcher interface