From d1d7bbced13aa0445379092642a08fe5ecbb5a01 Mon Sep 17 00:00:00 2001 From: ovechkin-dm Date: Wed, 12 Jun 2024 14:14:54 +0200 Subject: [PATCH] add more matchers (#70) --- mock/api.go | 161 ++++++++++++++++++++++++++++++++++++ tests/match/match_test.go | 170 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 331 insertions(+) diff --git a/mock/api.go b/mock/api.go index 5a1b786..83a55dd 100644 --- a/mock/api.go +++ b/mock/api.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "reflect" + "regexp" "strings" "github.com/ovechkin-dm/mockio/config" @@ -112,6 +113,166 @@ func AnyOfType[T any](t T) T { return Any[T]() } +// Nil returns matcher that matches nil argument. +// Example usage: +// +// WhenSingle(myMock.MyMethod(Nil[string]())).ThenReturn("bar") +func Nil[T any]() T { + m := registry.FunMatcher[T]("Nil", func(m []any, actual T) bool { + var d any = actual + return d == nil + }) + registry.AddMatcher(m) + var t T + return t +} + +// NotNil returns matcher that matches non-nil argument. +// Example usage: +// +// WhenSingle(myMock.MyMethod(NotNil[string]())).ThenReturn("bar") +func NotNil[T any]() T { + m := registry.FunMatcher[T]("NotNil", func(m []any, actual T) bool { + var d any = actual + return d != nil + }) + registry.AddMatcher(m) + var t T + return t +} + +// Regex returns matcher that matches string against provided pattern. +// Example usage: +// +// WhenSingle(myMock.MyMethod(Regex[string]("foo"))).ThenReturn("bar") +func Regex(pattern string) string { + re, err := regexp.Compile(pattern) + desc := fmt.Sprintf("Regex(%v)", pattern) + if err != nil { + desc = fmt.Sprintf("InvalidRegex(%v)", pattern) + } + m := registry.FunMatcher(desc, func(m []any, actual string) bool { + return err == nil && re.MatchString(actual) + }) + registry.AddMatcher(m) + return "" +} + +// Substring returns matcher that matches any string that contains specified substring. +// Example usage: +// +// WhenSingle(myMock.MyMethod(Substring("foo"))).ThenReturn("bar") +func Substring(value string) string { + desc := fmt.Sprintf("Substring(%v)", value) + m := registry.FunMatcher(desc, func(m []any, actual string) bool { + return strings.Contains(actual, value) + }) + registry.AddMatcher(m) + return "" +} + +// SliceLen returns matcher that matches any slice of length n. +// Example usage: +// +// WhenSingle(myMock.MyMethod(SliceLen(10))).ThenReturn("bar") +func SliceLen[T any](value int) []T { + desc := fmt.Sprintf("SliceLen(%v)", value) + m := registry.FunMatcher(desc, func(m []any, actual []T) bool { + return len(actual) == value + }) + registry.AddMatcher(m) + var t []T + return t +} + +// MapLen returns matcher that matches any map of length n. +// Example usage: +// +// WhenSingle(myMock.MyMethod(MapLen(10))).ThenReturn("bar") +func MapLen[K comparable, V any](value int) map[K]V { + desc := fmt.Sprintf("MapLen(%v)", value) + m := registry.FunMatcher(desc, func(m []any, actual map[K]V) bool { + return len(actual) == value + }) + registry.AddMatcher(m) + var t map[K]V + return t +} + +// SliceContains returns matcher that matches any slice that contains specified values. +// Example usage: +// +// WhenSingle(myMock.MyMethod(SliceContains("foo", "bar"))).ThenReturn("baz") +func SliceContains[T any](values ...T) []T { + desc := fmt.Sprintf("SliceContains(%v)", values) + m := registry.FunMatcher(desc, func(m []any, actual []T) bool { + amap := make(map[any]struct{}) + for _, v := range actual { + amap[v] = struct{}{} + } + for _, v := range values { + _, ok := amap[v] + if !ok { + return false + } + } + return true + }) + registry.AddMatcher(m) + var t []T + return t +} + +// MapContains returns matcher that matches any map that contains specified keys. +// Example usage: +// +// WhenSingle(myMock.MyMethod(MapContains("foo", "bar"))).ThenReturn("baz") +func MapContains[K comparable, V any](values ...K) map[K]V { + desc := fmt.Sprintf("MapContains(%v)", values) + m := registry.FunMatcher(desc, func(m []any, actual map[K]V) bool { + for _, v := range values { + _, ok := actual[v] + if !ok { + return false + } + } + return true + }) + registry.AddMatcher(m) + var t map[K]V + return t +} + +// SliceEqualUnordered returns matcher that matches slice with same values without taking order of elements into account. +// Example usage: +// +// WhenSingle(myMock.MyMethod(SliceEqualUnordered([]int{2,1}))).ThenReturn("baz") +func SliceEqualUnordered[T any](values []T) []T { + desc := fmt.Sprintf("EqualUnordered(%v)", values) + vmap := make(map[any]struct{}) + for _, v := range values { + vmap[v] = struct{}{} + } + m := registry.FunMatcher(desc, func(m []any, actual []T) bool { + if len(vmap) != len(values) { + return false + } + if len(actual) != len(values) { + return false + } + for _, v := range actual { + _, ok := vmap[v] + if !ok { + return false + } + } + return true + }) + registry.AddMatcher(m) + var t []T + return t +} + // Exact returns a matcher that matches values of type T that are equal to the provided value. // The value passed to Exact must be comparable with values of type T. // diff --git a/tests/match/match_test.go b/tests/match/match_test.go index e077ec2..bc896f9 100644 --- a/tests/match/match_test.go +++ b/tests/match/match_test.go @@ -24,6 +24,14 @@ type MyInterface interface { Test(m *MyStruct) int } +type SliceInterface interface { + Test(m []int) int +} + +type MapInterface interface { + Test(m map[int]int) int +} + func TestAny(t *testing.T) { r := common.NewMockReporter(t) SetUp(r) @@ -142,6 +150,168 @@ func TestDeepEqual(t *testing.T) { r.AssertEqual(result, 9) } +func TestNilMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[Iface]() + WhenSingle(m.Test(Nil[any]())).ThenReturn(true) + ret := m.Test(nil) + r.AssertEqual(true, ret) +} + +func TestNilNoMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[Iface]() + WhenSingle(m.Test(Nil[any]())).ThenReturn(true) + ret := m.Test(10) + r.AssertEqual(false, ret) +} + +func TestSubstringMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[Iface]() + WhenSingle(m.Test(Substring("test"))).ThenReturn(true) + ret := m.Test("123test123") + r.AssertEqual(true, ret) +} + +func TestSubstringNoMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[Iface]() + WhenSingle(m.Test(Substring("test321"))).ThenReturn(true) + ret := m.Test("123test123") + r.AssertEqual(false, ret) +} + +func TestNotNilMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[Iface]() + WhenSingle(m.Test(NotNil[any]())).ThenReturn(true) + ret := m.Test(10) + r.AssertEqual(true, ret) +} + +func TestNotNilNoMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[Iface]() + WhenSingle(m.Test(NotNil[any]())).ThenReturn(true) + ret := m.Test(nil) + r.AssertEqual(false, ret) +} + +func TestRegexMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[Iface]() + WhenSingle(m.Test(Regex("test"))).ThenReturn(true) + ret := m.Test("123test123") + r.AssertEqual(true, ret) +} + +func TestRegexNoMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[Iface]() + WhenSingle(m.Test(Regex("test321"))).ThenReturn(true) + ret := m.Test("123test123") + r.AssertEqual(false, ret) +} + +func TestSliceLenMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[SliceInterface]() + WhenSingle(m.Test(SliceLen[int](3))).ThenReturn(3) + ret := m.Test([]int{1, 2, 3}) + r.AssertEqual(3, ret) +} + +func TestSliceLenNoMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[SliceInterface]() + WhenSingle(m.Test(SliceLen[int](4))).ThenReturn(3) + ret := m.Test([]int{1, 2, 3}) + r.AssertEqual(0, ret) +} + +func TestMapLenMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[MapInterface]() + WhenSingle(m.Test(MapLen[int, int](3))).ThenReturn(3) + ret := m.Test(map[int]int{1: 1, 2: 2, 3: 3}) + r.AssertEqual(3, ret) +} + +func TestMapLenNoMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[MapInterface]() + WhenSingle(m.Test(MapLen[int, int](3))).ThenReturn(3) + ret := m.Test(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}) + r.AssertEqual(0, ret) +} + +func TestSliceContainsMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[SliceInterface]() + WhenSingle(m.Test(SliceContains[int](3))).ThenReturn(3) + ret := m.Test([]int{1, 2, 3}) + r.AssertEqual(3, ret) +} + +func TestSliceContainsNoMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[SliceInterface]() + WhenSingle(m.Test(SliceContains[int](4))).ThenReturn(3) + ret := m.Test([]int{1, 2, 3}) + r.AssertEqual(0, ret) +} + +func TestMapContainsMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[MapInterface]() + WhenSingle(m.Test(MapContains[int, int](3))).ThenReturn(3) + ret := m.Test(map[int]int{1: 1, 2: 2, 3: 3}) + r.AssertEqual(3, ret) +} + +func TestMapContainsNoMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[MapInterface]() + WhenSingle(m.Test(MapContains[int, int](4))).ThenReturn(3) + ret := m.Test(map[int]int{1: 1, 2: 2, 3: 3}) + r.AssertEqual(0, ret) +} + +func TestSliceEqualUnorderedMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[SliceInterface]() + WhenSingle(m.Test(SliceEqualUnordered[int]([]int{1, 2, 3}))).ThenReturn(3) + ret := m.Test([]int{3, 2, 1}) + r.AssertEqual(3, ret) +} + +func TestSliceEqualUnorderedNoMatch(t *testing.T) { + r := common.NewMockReporter(t) + SetUp(r) + m := Mock[SliceInterface]() + WhenSingle(m.Test(SliceEqualUnordered[int]([]int{1, 2, 3}))).ThenReturn(3) + ret := m.Test([]int{3, 2, 1, 4}) + r.AssertEqual(0, ret) +} + func TestUnexpectedUseOfMatchers(t *testing.T) { r := common.NewMockReporter(t) SetUp(r)