Skip to content
This repository was archived by the owner on Feb 17, 2025. It is now read-only.

Commit c46255d

Browse files
committed
Add slice assertions
1 parent a28eb88 commit c46255d

5 files changed

Lines changed: 235 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,17 @@ The main additions are the new assertions for
5959
via `FluentFunc` type:
6060
- `Panics`
6161
- `NotPanic`
62-
- Add `Async` and `Periodic` functions which provides following assertions
63-
via `FluentAsync` and `FluentPeriodic` types:
64-
- `Eventually`
65-
- `EventuallyContext`
62+
- Add `Slice` function which provides following assertions,
63+
in addition to `Any`, via `FluentSlice` types:
64+
- `Empty`
65+
- `NotEmpty`
66+
- `Equivalent`
67+
- `NotEquivalent`
68+
- `Contain`
69+
- `NotContain`
70+
- `Any`
71+
- `All`
72+
- `None`
6673
- Add `FailureMessage.Prefix` method together with `And` and `Or` functions
6774
to facilitate creating complex assertions.
6875

any.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ func (x FluentAny[T]) Should(pred func(got T) bool) FailureMessage {
2626
if pred(x.Got) {
2727
return ""
2828
}
29-
return FailureMessage(fmt.Sprintf("object does not meet the predicate criteria\ngot: %#v", x.Got))
29+
return FailureMessage(fmt.Sprintf("object does not meet the predicate criteria\ngot: %+v", x.Got))
3030
}
3131

3232
// ShouldNot tests if the object does not the predicate criteria.
3333
func (x FluentAny[T]) ShouldNot(fn func(got T) bool) FailureMessage {
3434
if !fn(x.Got) {
3535
return ""
3636
}
37-
return FailureMessage(fmt.Sprintf("object meets the predicate criteria\ngot: %#v", x.Got))
37+
return FailureMessage(fmt.Sprintf("object meets the predicate criteria\ngot: %+v", x.Got))
3838
}
3939

4040
// DeepEqual tests if the objects are deep equal using github.com/google/go-cmp/cmp.
@@ -52,5 +52,5 @@ func (x FluentAny[T]) NotDeepEqual(obj T, opts ...cmp.Option) FailureMessage {
5252
if !ok {
5353
return ""
5454
}
55-
return FailureMessage(fmt.Sprintf("the objects are equal\ngot: %#v", x.Got))
55+
return FailureMessage(fmt.Sprintf("the objects are equal\ngot: %+v", x.Got))
5656
}

comparable.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func (x FluentComparable[T]) Equal(want T) FailureMessage {
1717
if x.Got == want {
1818
return ""
1919
}
20-
return FailureMessage(fmt.Sprintf("the objects are not equal\ngot: %#v\nwant: %#v", x.Got, want))
20+
return FailureMessage(fmt.Sprintf("the objects are not equal\ngot: %+v\nwant: %+v", x.Got, want))
2121
}
2222

2323
// NotEqual tests the objects using != operator.
@@ -34,7 +34,7 @@ func (x FluentComparable[T]) Zero() FailureMessage {
3434
if want == x.Got {
3535
return ""
3636
}
37-
return FailureMessage(fmt.Sprintf("not a zero value\ngot: %#v", x.Got))
37+
return FailureMessage(fmt.Sprintf("not a zero value\ngot: %+v", x.Got))
3838
}
3939

4040
// NonZero tests if the object is a non-zero value.
@@ -43,5 +43,5 @@ func (x FluentComparable[T]) NonZero() FailureMessage {
4343
if want != x.Got {
4444
return ""
4545
}
46-
return FailureMessage(fmt.Sprintf("not a zero value\ngot: %#v", x.Got))
46+
return FailureMessage(fmt.Sprintf("not a zero value\ngot: %+v", x.Got))
4747
}

slice.go

Lines changed: 112 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package verify
22

3+
import (
4+
"fmt"
5+
6+
"github.com/google/go-cmp/cmp"
7+
)
8+
39
// FluentSlice encapsulates assertions for a slice.
410
type FluentSlice[T any] struct {
511
FluentAny[[]T]
@@ -10,24 +16,119 @@ func Slice[T any](got []T) FluentSlice[T] {
1016
return FluentSlice[T]{FluentAny[[]T]{got}}
1117
}
1218

13-
// TODO: Empty() FailureMessage
19+
// Empty banana.
20+
func (x FluentSlice[T]) Empty() FailureMessage {
21+
if len(x.Got) == 0 {
22+
return ""
23+
}
24+
return FailureMessage(fmt.Sprintf("not an empty slice\ngot: %+v", x.Got))
25+
}
26+
27+
// NotEmpty banana.
28+
func (x FluentSlice[T]) NotEmpty() FailureMessage {
29+
if len(x.Got) > 0 {
30+
return ""
31+
}
32+
return FailureMessage(fmt.Sprintf("an empty slice\ngot: %+v", x.Got))
33+
}
34+
35+
// Equivalent banana.
36+
func (x FluentSlice[T]) Equivalent(want []T, opts ...cmp.Option) FailureMessage {
37+
extraGot, extraWant := x.diff(want, opts)
38+
if len(extraGot) == 0 && len(extraWant) == 0 {
39+
return ""
40+
}
41+
return FailureMessage(fmt.Sprintf("not equivalent\nextra got: %+v\nextra want: %+v", extraGot, extraWant))
42+
}
1443

15-
// TODO: NotEmpty() FailureMessage
44+
// NotEquivalent banana.
45+
func (x FluentSlice[T]) NotEquivalent(want []T, opts ...cmp.Option) FailureMessage {
46+
extraGot, extraWant := x.diff(want, opts)
47+
if len(extraGot) != 0 || len(extraWant) != 0 {
48+
return ""
49+
}
50+
return FailureMessage(fmt.Sprintf("equivalent\ngot: %+v", extraGot))
51+
}
1652

17-
// TODO: Len(n int) FailureMessage
53+
func (x FluentSlice[T]) diff(want []T, opts []cmp.Option) (extraGot, extraWant []T) {
54+
aLen := len(x.Got)
55+
bLen := len(want)
1856

19-
// TODO: Equal(elements []T) FailureMessage
57+
// Mark indexes in list that we already used
58+
visited := make([]bool, bLen)
59+
for i := 0; i < aLen; i++ {
60+
found := false
61+
for j := 0; j < bLen; j++ {
62+
if visited[j] {
63+
continue
64+
}
65+
if cmp.Equal(want[j], x.Got[i], opts...) {
66+
visited[j] = true
67+
found = true
68+
break
69+
}
70+
}
71+
if !found {
72+
extraGot = append(extraGot, x.Got[i])
73+
}
74+
}
2075

21-
// TODO: NotEqual(elements []T) FailureMessage
76+
for j := 0; j < bLen; j++ {
77+
if visited[j] {
78+
continue
79+
}
80+
extraWant = append(extraWant, want[j])
81+
}
2282

23-
// TODO: Equivalent(elements []T) FailureMessage
83+
return
84+
}
2485

25-
// TODO: NotEquivalent(elements []T) FailureMessage
86+
// Contain banana.
87+
func (x FluentSlice[T]) Contain(item T, opts ...cmp.Option) FailureMessage {
88+
for _, v := range x.Got {
89+
if cmp.Equal(item, v, opts...) {
90+
return ""
91+
}
92+
}
93+
return FailureMessage(fmt.Sprintf("slice does not contain the item\ngot: %+v\nitem: %+v", x.Got, item))
94+
}
2695

27-
// TODO: Contain(elements ...T) FailureMessage
96+
// NotContain banana.
97+
func (x FluentSlice[T]) NotContain(item T, opts ...cmp.Option) FailureMessage {
98+
for _, v := range x.Got {
99+
if cmp.Equal(item, v, opts...) {
100+
return FailureMessage(fmt.Sprintf("slice contains the item\ngot: %+v\nitem: %+v", x.Got, item))
101+
}
102+
}
103+
return ""
104+
}
28105

29-
// TODO: NotContain(elements ...T) FailureMessage
106+
// Any banana.
107+
func (x FluentSlice[T]) Any(predicate func(T) bool) FailureMessage {
108+
for _, v := range x.Got {
109+
if predicate(v) {
110+
return ""
111+
}
112+
}
113+
return FailureMessage(fmt.Sprintf("none item does meet the predicate criteria\ngot: %+v", x.Got))
114+
}
30115

31-
// TODO: Any(func(T) bool) FailureMessage
116+
// All banana.
117+
func (x FluentSlice[T]) All(predicate func(T) bool) FailureMessage {
118+
for _, v := range x.Got {
119+
if !predicate(v) {
120+
return FailureMessage(fmt.Sprintf("an item does not meet the predicate criteria\ngot: %+v\nitem: %+v", x.Got, v))
121+
}
122+
}
123+
return ""
124+
}
32125

33-
// TODO: All(func(T) bool) FailureMessage
126+
// None banana.
127+
func (x FluentSlice[T]) None(predicate func(T) bool) FailureMessage {
128+
for _, v := range x.Got {
129+
if predicate(v) {
130+
return FailureMessage(fmt.Sprintf("an item meets the predicate criteria\ngot: %+v\nitem: %+v", x.Got, v))
131+
}
132+
}
133+
return ""
134+
}

slice_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,110 @@ func TestSlice(t *testing.T) {
2020
got := verify.Slice(want).FluentAny.Got // type embedding done properly
2121
assertEqual(t, got, want)
2222
})
23+
24+
t.Run("Empty", func(t *testing.T) {
25+
t.Run("Passed", func(t *testing.T) {
26+
got := verify.Slice([]A{}).Empty()
27+
assertPassed(t, got)
28+
})
29+
t.Run("Failed", func(t *testing.T) {
30+
got := verify.Slice([]A{{}}).Empty()
31+
assertFailed(t, got, "not an empty slice")
32+
})
33+
})
34+
t.Run("NotEmpty", func(t *testing.T) {
35+
t.Run("Passed", func(t *testing.T) {
36+
got := verify.Slice([]A{{}}).NotEmpty()
37+
assertPassed(t, got)
38+
})
39+
t.Run("Failed", func(t *testing.T) {
40+
got := verify.Slice([]A{}).NotEmpty()
41+
assertFailed(t, got, "an empty slice")
42+
})
43+
})
44+
45+
list := []A{
46+
{Str: "text", Bool: true, Slice: []int{1, 2, 3}},
47+
{Slice: []int{9, 8, 7}},
48+
}
49+
eq := []A{
50+
{Slice: []int{9, 8, 7}},
51+
{Str: "text", Bool: true, Slice: []int{1, 2, 3}},
52+
}
53+
notEq := []A{
54+
{Slice: []int{0, 0, 0}},
55+
{Str: "text", Bool: true, Slice: []int{1, 2, 3}},
56+
}
57+
t.Run("Equivalent", func(t *testing.T) {
58+
t.Run("Passed", func(t *testing.T) {
59+
got := verify.Slice(list).Equivalent(eq)
60+
assertPassed(t, got)
61+
})
62+
t.Run("Failed", func(t *testing.T) {
63+
got := verify.Slice(list).Equivalent(notEq)
64+
assertFailed(t, got, "not equivalent")
65+
})
66+
})
67+
t.Run("NotEquivalent", func(t *testing.T) {
68+
t.Run("Passed", func(t *testing.T) {
69+
got := verify.Slice(list).NotEquivalent(notEq)
70+
assertPassed(t, got)
71+
})
72+
t.Run("Failed", func(t *testing.T) {
73+
got := verify.Slice(list).NotEquivalent(eq)
74+
assertFailed(t, got, "equivalent")
75+
})
76+
})
77+
78+
t.Run("Contain", func(t *testing.T) {
79+
t.Run("Passed", func(t *testing.T) {
80+
got := verify.Slice(list).Contain(A{Str: "text", Bool: true, Slice: []int{1, 2, 3}})
81+
assertPassed(t, got)
82+
})
83+
t.Run("Failed", func(t *testing.T) {
84+
got := verify.Slice(list).Contain(A{})
85+
assertFailed(t, got, "slice does not contain the item")
86+
})
87+
})
88+
t.Run("NotContain", func(t *testing.T) {
89+
t.Run("Passed", func(t *testing.T) {
90+
got := verify.Slice(list).NotContain(A{})
91+
assertPassed(t, got)
92+
})
93+
t.Run("Failed", func(t *testing.T) {
94+
got := verify.Slice(list).NotContain(A{Str: "text", Bool: true, Slice: []int{1, 2, 3}})
95+
assertFailed(t, got, "slice contains the item")
96+
})
97+
})
98+
99+
t.Run("Any", func(t *testing.T) {
100+
t.Run("Passed", func(t *testing.T) {
101+
got := verify.Slice(list).Any(func(a A) bool { return true })
102+
assertPassed(t, got)
103+
})
104+
t.Run("Failed", func(t *testing.T) {
105+
got := verify.Slice(list).Any(func(a A) bool { return false })
106+
assertFailed(t, got, "none item does meet the predicate criteria")
107+
})
108+
})
109+
t.Run("All", func(t *testing.T) {
110+
t.Run("Passed", func(t *testing.T) {
111+
got := verify.Slice(list).All(func(a A) bool { return true })
112+
assertPassed(t, got)
113+
})
114+
t.Run("Failed", func(t *testing.T) {
115+
got := verify.Slice(list).All(func(a A) bool { return false })
116+
assertFailed(t, got, "an item does not meet the predicate criteria")
117+
})
118+
})
119+
t.Run("None", func(t *testing.T) {
120+
t.Run("Passed", func(t *testing.T) {
121+
got := verify.Slice(list).None(func(a A) bool { return false })
122+
assertPassed(t, got)
123+
})
124+
t.Run("Failed", func(t *testing.T) {
125+
got := verify.Slice(list).None(func(a A) bool { return true })
126+
assertFailed(t, got, "an item meets the predicate criteria")
127+
})
128+
})
23129
}

0 commit comments

Comments
 (0)