Skip to content

Commit 94a7ac3

Browse files
authored
feat: add Regex() matcher (#114)
Add a matcher for matching against regular expressions
1 parent b233940 commit 94a7ac3

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

gomock/matchers.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package gomock
1717
import (
1818
"fmt"
1919
"reflect"
20+
"regexp"
2021
"strings"
2122
)
2223

@@ -168,6 +169,25 @@ func (n notMatcher) String() string {
168169
return "not(" + n.m.String() + ")"
169170
}
170171

172+
type regexMatcher struct {
173+
regex *regexp.Regexp
174+
}
175+
176+
func (m regexMatcher) Matches(x any) bool {
177+
switch t := x.(type) {
178+
case string:
179+
return m.regex.MatchString(t)
180+
case []byte:
181+
return m.regex.Match(t)
182+
default:
183+
return false
184+
}
185+
}
186+
187+
func (m regexMatcher) String() string {
188+
return "matches regex " + m.regex.String()
189+
}
190+
171191
type assignableToTypeOfMatcher struct {
172192
targetType reflect.Type
173193
}
@@ -382,6 +402,18 @@ func Not(x any) Matcher {
382402
return notMatcher{Eq(x)}
383403
}
384404

405+
// Regex checks whether parameter matches the associated regex.
406+
//
407+
// Example usage:
408+
//
409+
// Regex("[0-9]{2}:[0-9]{2}").Matches("23:02") // returns true
410+
// Regex("[0-9]{2}:[0-9]{2}").Matches([]byte{'2', '3', ':', '0', '2'}) // returns true
411+
// Regex("[0-9]{2}:[0-9]{2}").Matches("hello world") // returns false
412+
// Regex("[0-9]{2}").Matches(21) // returns false as it's not a valid type
413+
func Regex(regexStr string) Matcher {
414+
return regexMatcher{regex: regexp.MustCompile(regexStr)}
415+
}
416+
385417
// AssignableToTypeOf is a Matcher that matches if the parameter to the mock
386418
// function is assignable to the type of the parameter to this function.
387419
//

gomock/matchers_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func TestMatchers(t *testing.T) {
4747
[]e{nil, (error)(nil), (chan bool)(nil), (*int)(nil)},
4848
[]e{"", 0, make(chan bool), errors.New("err"), new(int)}},
4949
{"test Not", gomock.Not(gomock.Eq(4)), []e{3, "blah", nil, int64(4)}, []e{4}},
50+
{"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")}},
5051
{"test All", gomock.All(gomock.Any(), gomock.Eq(4)), []e{4}, []e{3, "blah", nil, int64(4)}},
5152
{"test Len", gomock.Len(2),
5253
[]e{[]int{1, 2}, "ab", map[string]int{"a": 0, "b": 1}, [2]string{"a", "b"}},
@@ -92,6 +93,65 @@ func TestNotMatcher(t *testing.T) {
9293
}
9394
}
9495

96+
// A more thorough test of regexMatcher
97+
func TestRegexMatcher(t *testing.T) {
98+
tests := []struct {
99+
name string
100+
regex string
101+
input any
102+
wantMatch bool
103+
wantStringResponse string
104+
shouldPanic bool
105+
}{
106+
{
107+
name: "match for whole num regex with start and end position matching",
108+
regex: "^\\d+$",
109+
input: "2302",
110+
wantMatch: true,
111+
wantStringResponse: "matches regex ^\\d+$",
112+
},
113+
{
114+
name: "match for valid regex with start and end position matching on longer string",
115+
regex: "^[0-9]{2}:[0-9]{2}$",
116+
input: "[23:02]: Hello world",
117+
wantMatch: false,
118+
wantStringResponse: "matches regex ^[0-9]{2}:[0-9]{2}$",
119+
},
120+
{
121+
name: "match for valid regex with struct as bytes",
122+
regex: `^{"id":[0-9]{2}}$`,
123+
input: []byte{'{', '"', 'i', 'd', '"', ':', '1', '2', '}'},
124+
wantMatch: true,
125+
wantStringResponse: `matches regex ^{"id":[0-9]{2}}$`,
126+
},
127+
{
128+
name: "should panic when regex fails to compile",
129+
regex: `^[0-9]\\?{2}:[0-9]{2}$`,
130+
shouldPanic: true,
131+
},
132+
}
133+
134+
for _, tt := range tests {
135+
t.Run(tt.name, func(t *testing.T) {
136+
if tt.shouldPanic {
137+
defer func() { _ = recover() }()
138+
}
139+
matcher := gomock.Regex(tt.regex)
140+
141+
if tt.shouldPanic {
142+
t.Errorf("test did not panic, and should have")
143+
}
144+
145+
if got := matcher.Matches(tt.input); got != tt.wantMatch {
146+
t.Errorf("got = %v, wantMatch = %v", got, tt.wantMatch)
147+
}
148+
if gotStr := matcher.String(); gotStr != tt.wantStringResponse {
149+
t.Errorf("got string = %v, want string = %v", gotStr, tt.wantStringResponse)
150+
}
151+
})
152+
}
153+
}
154+
95155
type Dog struct {
96156
Breed, Name string
97157
}

0 commit comments

Comments
 (0)