diff --git a/gomock/matchers.go b/gomock/matchers.go index 079eae9..f090872 100644 --- a/gomock/matchers.go +++ b/gomock/matchers.go @@ -17,6 +17,7 @@ package gomock import ( "fmt" "reflect" + "regexp" "strings" ) @@ -168,6 +169,26 @@ func (n notMatcher) String() string { return "not(" + n.m.String() + ")" } +type regexMatcher struct { + regex string +} + +func (m regexMatcher) Matches(x any) bool { + str, ok := x.(string) + if !ok { + return false + } + match, err := regexp.MatchString(m.regex, str) + if !match || err != nil { + return false + } + return true +} + +func (m regexMatcher) String() string { + return "matching regex " + m.regex +} + type assignableToTypeOfMatcher struct { targetType reflect.Type } @@ -382,6 +403,17 @@ func Not(x any) Matcher { return notMatcher{Eq(x)} } +// Regex checks whether a string parameter matches the associated regex. +// +// Example usage: +// +// Regex("[0-9]{2}:[0-9]{2}").Matches("23:02") // returns true +// Regex("[0-9]{2}:[0-9]{2}").Matches("hello world") // returns false +// Regex("[0-9]{2}").Matches(21) // returns false as it's not a string +func Regex(regexStr string) Matcher { + return regexMatcher{regex: regexStr} +} + // AssignableToTypeOf is a Matcher that matches if the parameter to the mock // function is assignable to the type of the parameter to this function. // diff --git a/gomock/matchers_test.go b/gomock/matchers_test.go index 0380aa8..2731d0b 100644 --- a/gomock/matchers_test.go +++ b/gomock/matchers_test.go @@ -47,6 +47,7 @@ func TestMatchers(t *testing.T) { []e{nil, (error)(nil), (chan bool)(nil), (*int)(nil)}, []e{"", 0, make(chan bool), errors.New("err"), new(int)}}, {"test Not", gomock.Not(gomock.Eq(4)), []e{3, "blah", nil, int64(4)}, []e{4}}, + {"test Regex", gomock.Regex("[0-9]{2}:[0-9]{2}"), []e{"23:02", "[23:02]: Hello world"}, []e{4, "23-02", "hello world", true}}, {"test All", gomock.All(gomock.Any(), gomock.Eq(4)), []e{4}, []e{3, "blah", nil, int64(4)}}, {"test Len", gomock.Len(2), []e{[]int{1, 2}, "ab", map[string]int{"a": 0, "b": 1}, [2]string{"a", "b"}}, @@ -92,6 +93,52 @@ func TestNotMatcher(t *testing.T) { } } +// A more thorough test of regexMatcher +func TestRegexMatcher(t *testing.T) { + tests := []struct { + name string + regex string + input any + wantMatch bool + wantStringResponse string + }{ + { + name: "match for whole num regex with start and end position matching", + regex: "^\\d+$", + input: "2302", + wantMatch: true, + wantStringResponse: "matching regex ^\\d+$", + }, + { + name: "match for valid regex with start and end position matching on longer string", + regex: "^[0-9]{2}:[0-9]{2}$", + input: "[23:02]: Hello world", + wantMatch: false, + wantStringResponse: "matching regex ^[0-9]{2}:[0-9]{2}$", + }, + { + name: "match for invalid regex", + regex: "^[0-9{2}:[0-9]{2}$", + input: "23:02", + wantMatch: false, + wantStringResponse: "matching regex ^[0-9{2}:[0-9]{2}$", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + matcher := gomock.Regex(tt.regex) + + if got := matcher.Matches(tt.input); got != tt.wantMatch { + t.Errorf("got = %v, wantMatch = %v", got, tt.wantMatch) + } + if gotStr := matcher.String(); gotStr != tt.wantStringResponse { + t.Errorf("got string = %v, want string = %v", gotStr, tt.wantStringResponse) + } + }) + } +} + type Dog struct { Breed, Name string }