Skip to content

Commit

Permalink
feat: add Regex() matcher (#114)
Browse files Browse the repository at this point in the history
Add a matcher for matching against regular expressions
  • Loading branch information
merrett010 committed Oct 30, 2023
1 parent b233940 commit 94a7ac3
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 0 deletions.
32 changes: 32 additions & 0 deletions gomock/matchers.go
Expand Up @@ -17,6 +17,7 @@ package gomock
import (
"fmt"
"reflect"
"regexp"
"strings"
)

Expand Down Expand Up @@ -168,6 +169,25 @@ func (n notMatcher) String() string {
return "not(" + n.m.String() + ")"
}

type regexMatcher struct {
regex *regexp.Regexp
}

func (m regexMatcher) Matches(x any) bool {
switch t := x.(type) {
case string:
return m.regex.MatchString(t)
case []byte:
return m.regex.Match(t)
default:
return false
}
}

func (m regexMatcher) String() string {
return "matches regex " + m.regex.String()
}

type assignableToTypeOfMatcher struct {
targetType reflect.Type
}
Expand Down Expand Up @@ -382,6 +402,18 @@ func Not(x any) Matcher {
return notMatcher{Eq(x)}
}

// Regex checks whether 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([]byte{'2', '3', ':', '0', '2'}) // 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 valid type
func Regex(regexStr string) Matcher {
return regexMatcher{regex: regexp.MustCompile(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.
//
Expand Down
60 changes: 60 additions & 0 deletions gomock/matchers_test.go
Expand Up @@ -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", []byte("23:02")}, []e{4, "23-02", "hello world", true, []byte("23-02")}},
{"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"}},
Expand Down Expand Up @@ -92,6 +93,65 @@ 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
shouldPanic bool
}{
{
name: "match for whole num regex with start and end position matching",
regex: "^\\d+$",
input: "2302",
wantMatch: true,
wantStringResponse: "matches 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: "matches regex ^[0-9]{2}:[0-9]{2}$",
},
{
name: "match for valid regex with struct as bytes",
regex: `^{"id":[0-9]{2}}$`,
input: []byte{'{', '"', 'i', 'd', '"', ':', '1', '2', '}'},
wantMatch: true,
wantStringResponse: `matches regex ^{"id":[0-9]{2}}$`,
},
{
name: "should panic when regex fails to compile",
regex: `^[0-9]\\?{2}:[0-9]{2}$`,
shouldPanic: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.shouldPanic {
defer func() { _ = recover() }()
}
matcher := gomock.Regex(tt.regex)

if tt.shouldPanic {
t.Errorf("test did not panic, and should have")
}

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
}
Expand Down

0 comments on commit 94a7ac3

Please sign in to comment.