From 1a967348d89397af48a6f789b0033fcd0cbfefc1 Mon Sep 17 00:00:00 2001 From: Adam Luzsi Date: Wed, 8 Jun 2022 00:59:59 +0200 Subject: [PATCH] add package level assertion functions to assert package This will make the assert package align with other popular assertion packages in terms of conventions. --- assert/Asserter.go | 111 ++++++++++------- assert/Asserter_test.go | 6 + assert/example_test.go | 97 +++++++++++++++ assert/pkgfunc.go | 61 ++++++++++ assert/pkgfunc_test.go | 257 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 486 insertions(+), 46 deletions(-) create mode 100644 assert/pkgfunc.go create mode 100644 assert/pkgfunc_test.go diff --git a/assert/Asserter.go b/assert/Asserter.go index c722802..c3395c8 100644 --- a/assert/Asserter.go +++ b/assert/Asserter.go @@ -34,11 +34,11 @@ type Asserter struct { func (a Asserter) try(blk func(a Asserter)) (ok bool) { a.TB.Helper() var failed bool - blk(Asserter{TB: a.TB, Fn: func(args ...interface{}) { failed = true }}) + blk(Asserter{TB: a.TB, Fn: func(args ...any) { failed = true }}) return !failed } -func (a Asserter) True(v bool, msg ...interface{}) { +func (a Asserter) True(v bool, msg ...any) { a.TB.Helper() if v { return @@ -56,7 +56,7 @@ func (a Asserter) True(v bool, msg ...interface{}) { }.String()) } -func (a Asserter) False(v bool, msg ...interface{}) { +func (a Asserter) False(v bool, msg ...any) { a.TB.Helper() if !a.try(func(a Asserter) { a.True(v) }) { return @@ -74,7 +74,7 @@ func (a Asserter) False(v bool, msg ...interface{}) { }.String()) } -func (a Asserter) Nil(v interface{}, msg ...interface{}) { +func (a Asserter) Nil(v any, msg ...any) { a.TB.Helper() if v == nil { return @@ -99,7 +99,7 @@ func (a Asserter) Nil(v interface{}, msg ...interface{}) { }) } -func (a Asserter) NotNil(v interface{}, msg ...interface{}) { +func (a Asserter) NotNil(v any, msg ...any) { a.TB.Helper() if !a.try(func(a Asserter) { a.Nil(v) }) { return @@ -111,13 +111,13 @@ func (a Asserter) NotNil(v interface{}, msg ...interface{}) { }) } -func (a Asserter) hasPanicked(blk func()) (panicValue interface{}, ok bool) { +func (a Asserter) hasPanicked(blk func()) (panicValue any, ok bool) { a.TB.Helper() panicValue, finished := internal.Recover(blk) return panicValue, !finished } -func (a Asserter) Panic(blk func(), msg ...interface{}) (panicValue interface{}) { +func (a Asserter) Panic(blk func(), msg ...any) (panicValue any) { a.TB.Helper() panicValue, ok := a.hasPanicked(blk) if ok { @@ -131,7 +131,7 @@ func (a Asserter) Panic(blk func(), msg ...interface{}) (panicValue interface{}) return nil } -func (a Asserter) NotPanic(blk func(), msg ...interface{}) { +func (a Asserter) NotPanic(blk func(), msg ...any) { a.TB.Helper() panicValue, ok := a.hasPanicked(blk) if !ok { @@ -158,7 +158,7 @@ type equalableWithError[T any] interface { IsEqual(oth T) (bool, error) } -func (a Asserter) Equal(expected, actually interface{}, msg ...interface{}) { +func (a Asserter) Equal(expected, actually any, msg ...any) { a.TB.Helper() if a.eq(expected, actually) { return @@ -179,7 +179,7 @@ func (a Asserter) Equal(expected, actually interface{}, msg ...interface{}) { }.String()) } -func (a Asserter) NotEqual(v, oth interface{}, msg ...interface{}) { +func (a Asserter) NotEqual(v, oth any, msg ...any) { a.TB.Helper() if !a.try(func(a Asserter) { a.Equal(v, oth) }) { return @@ -201,7 +201,7 @@ func (a Asserter) NotEqual(v, oth interface{}, msg ...interface{}) { }.String()) } -func (a Asserter) eq(exp, act interface{}) bool { +func (a Asserter) eq(exp, act any) bool { if isEqual, ok := a.tryIsEqual(exp, act); ok { return isEqual } @@ -209,7 +209,7 @@ func (a Asserter) eq(exp, act interface{}) bool { return reflect.DeepEqual(exp, act) } -func (a Asserter) tryIsEqual(exp, act interface{}) (isEqual bool, ok bool) { +func (a Asserter) tryIsEqual(exp, act any) (isEqual bool, ok bool) { defer func() { recover() }() expRV := reflect.ValueOf(exp) actRV := reflect.ValueOf(act) @@ -246,25 +246,25 @@ func (a Asserter) tryIsEqual(exp, act interface{}) (isEqual bool, ok bool) { } } -func (a Asserter) Contain(src, has interface{}, msg ...interface{}) { +func (a Asserter) Contain(haystack, needle any, msg ...any) { a.TB.Helper() - rSrc := reflect.ValueOf(src) - rHas := reflect.ValueOf(has) + rSrc := reflect.ValueOf(haystack) + rHas := reflect.ValueOf(needle) if !rSrc.IsValid() { a.Fn(fmterror.Message{ - Method: "Contains", + Method: "Contain", Cause: "invalid source value", Values: []fmterror.Value{ - {Label: "value", Value: src}, + {Label: "value", Value: haystack}, }, }.String()) return } if !rHas.IsValid() { a.Fn(fmterror.Message{ - Method: "Contains", + Method: "Contain", Cause: `invalid "has" value`, - Values: []fmterror.Value{{Label: "value", Value: has}}, + Values: []fmterror.Value{{Label: "value", Value: needle}}, }.String()) return } @@ -287,27 +287,27 @@ func (a Asserter) Contain(src, has interface{}, msg ...interface{}) { default: panic(fmterror.Message{ - Method: "Contains", + Method: "Contain", Cause: "Unimplemented scenario or type mismatch.", Values: []fmterror.Value{ { Label: "source-type", - Value: fmt.Sprintf("%T", src), + Value: fmt.Sprintf("%T", haystack), }, { Label: "value-type", - Value: fmt.Sprintf("%T", has), + Value: fmt.Sprintf("%T", needle), }, }, }.String()) } } -func (a Asserter) failContains(src, sub interface{}, msg ...interface{}) { +func (a Asserter) failContains(src, sub any, msg ...any) { a.TB.Helper() a.Fn(fmterror.Message{ - Method: "Contains", + Method: "Contain", Cause: "Source doesn't contains expected value(s).", Values: []fmterror.Value{ { @@ -323,7 +323,7 @@ func (a Asserter) failContains(src, sub interface{}, msg ...interface{}) { }.String()) } -func (a Asserter) sliceContainsValue(slice, value reflect.Value, msg []interface{}) { +func (a Asserter) sliceContainsValue(slice, value reflect.Value, msg []any) { a.TB.Helper() var found bool for i := 0; i < slice.Len(); i++ { @@ -336,7 +336,7 @@ func (a Asserter) sliceContainsValue(slice, value reflect.Value, msg []interface return } a.Fn(fmterror.Message{ - Method: "Contains", + Method: "Contain", Cause: "Couldn't find the expected value in the source slice", Values: []fmterror.Value{ { @@ -352,14 +352,14 @@ func (a Asserter) sliceContainsValue(slice, value reflect.Value, msg []interface }) } -func (a Asserter) sliceContainsSubSlice(slice, sub reflect.Value, msg []interface{}) { +func (a Asserter) sliceContainsSubSlice(slice, sub reflect.Value, msg []any) { a.TB.Helper() failWithNotEqual := func() { a.failContains(slice.Interface(), sub.Interface(), msg...) } //if slice.Kind() != reflect.Slice || sub.Kind() != reflect.Slice { // a.Fn(fmterror.Message{ - // Method: "Contains", + // Method: "Contain", // Cause: "Invalid slice type(s).", // Values: []fmterror.Value{ // { @@ -377,7 +377,7 @@ func (a Asserter) sliceContainsSubSlice(slice, sub reflect.Value, msg []interfac //} if slice.Len() < sub.Len() { a.Fn(fmterror.Message{ - Method: "Contains", + Method: "Contain", Cause: "Source slice is smaller than sub slice.", Values: []fmterror.Value{ { @@ -425,12 +425,12 @@ searching: } } -func (a Asserter) mapContainsSubMap(src reflect.Value, has reflect.Value, msg []interface{}) { +func (a Asserter) mapContainsSubMap(src reflect.Value, has reflect.Value, msg []any) { for _, key := range has.MapKeys() { srcValue := src.MapIndex(key) if !srcValue.IsValid() { a.Fn(fmterror.Message{ - Method: "Contains", + Method: "Contain", Cause: "Source doesn't contains the other map.", Values: []fmterror.Value{ { @@ -448,7 +448,7 @@ func (a Asserter) mapContainsSubMap(src reflect.Value, has reflect.Value, msg [] } if !a.eq(srcValue.Interface(), has.MapIndex(key).Interface()) { a.Fn(fmterror.Message{ - Method: "Contains", + Method: "Contain", Cause: "Source has the key but with different value.", Values: []fmterror.Value{ { @@ -467,13 +467,13 @@ func (a Asserter) mapContainsSubMap(src reflect.Value, has reflect.Value, msg [] } } -func (a Asserter) stringContainsSub(src reflect.Value, has reflect.Value, msg []interface{}) { +func (a Asserter) stringContainsSub(src reflect.Value, has reflect.Value, msg []any) { a.TB.Helper() if strings.Contains(fmt.Sprint(src.Interface()), fmt.Sprint(has.Interface())) { return } a.Fn(fmterror.Message{ - Method: "Contains", + Method: "Contain", Cause: "String doesn't include sub string.", Values: []fmterror.Value{ { @@ -489,9 +489,9 @@ func (a Asserter) stringContainsSub(src reflect.Value, has reflect.Value, msg [] }) } -func (a Asserter) NotContain(source, oth interface{}, msg ...interface{}) { +func (a Asserter) NotContain(haystack, v any, msg ...any) { a.TB.Helper() - if !a.try(func(a Asserter) { a.Contain(source, oth) }) { + if !a.try(func(a Asserter) { a.Contain(haystack, v) }) { return } a.Fn(fmterror.Message{ @@ -499,19 +499,19 @@ func (a Asserter) NotContain(source, oth interface{}, msg ...interface{}) { Cause: "Source contains the received value", Values: []fmterror.Value{ { - Label: "source", - Value: source, + Label: "haystack", + Value: haystack, }, { Label: "other", - Value: oth, + Value: v, }, }, UserMessage: msg, }) } -func (a Asserter) ContainExactly(expected, actual interface{}, msg ...interface{}) { +func (a Asserter) ContainExactly(expected, actual any, msg ...any) { a.TB.Helper() exp := reflect.ValueOf(expected) @@ -550,6 +550,7 @@ func (a Asserter) ContainExactly(expected, actual interface{}, msg ...interface{ a.containExactlyMap(exp, act, msg) default: + // TODO: maybe use Equal as default approach? panic(fmterror.Message{ Method: "ContainExactly", Cause: "Unimplemented scenario or type mismatch.", @@ -567,7 +568,7 @@ func (a Asserter) ContainExactly(expected, actual interface{}, msg ...interface{ } } -func (a Asserter) containExactlyMap(exp reflect.Value, act reflect.Value, msg []interface{}) { +func (a Asserter) containExactlyMap(exp reflect.Value, act reflect.Value, msg []any) { a.TB.Helper() if a.eq(exp.Interface(), act.Interface()) { @@ -584,9 +585,27 @@ func (a Asserter) containExactlyMap(exp reflect.Value, act reflect.Value, msg [] }) } -func (a Asserter) containExactlySlice(exp reflect.Value, act reflect.Value, msg []interface{}) { +func (a Asserter) containExactlySlice(exp reflect.Value, act reflect.Value, msg []any) { a.TB.Helper() + if exp.Len() != act.Len() { + a.Fn(fmterror.Message{ + Method: "ContainExactly", + Cause: "Element count doesn't match", + Values: []fmterror.Value{ + { + Label: "actual:", + Value: act.Interface(), + }, + { + Label: "value", + Value: exp.Interface(), + }, + }, + UserMessage: msg, + }) + } + for i := 0; i < exp.Len(); i++ { expectedValue := exp.Index(i).Interface() @@ -618,7 +637,7 @@ func (a Asserter) containExactlySlice(exp reflect.Value, act reflect.Value, msg } } -func (a Asserter) AnyOf(blk func(a *AnyOf), msg ...interface{}) { +func (a Asserter) AnyOf(blk func(a *AnyOf), msg ...any) { a.TB.Helper() anyOf := &AnyOf{TB: a.TB, Fn: a.Fn} defer anyOf.Finish(msg...) @@ -650,7 +669,7 @@ func (a Asserter) isEmpty(v any) bool { } // Empty gets whether the specified value is considered empty. -func (a Asserter) Empty(v interface{}, msg ...interface{}) { +func (a Asserter) Empty(v any, msg ...any) { a.TB.Helper() if a.isEmpty(v) { return @@ -666,7 +685,7 @@ func (a Asserter) Empty(v interface{}, msg ...interface{}) { } // NotEmpty gets whether the specified value is considered empty. -func (a Asserter) NotEmpty(v interface{}, msg ...interface{}) { +func (a Asserter) NotEmpty(v any, msg ...any) { a.TB.Helper() if !a.try(func(a Asserter) { a.Empty(v) }) { return @@ -684,7 +703,7 @@ func (a Asserter) NotEmpty(v interface{}, msg ...interface{}) { // ErrorIs allows you to assert an error value by an expectation. // if the implementation of the test subject later changes, and for example, it starts to use wrapping, // this should not be an issue as the IsEqualErr's error chain is also matched against the expectation. -func (a Asserter) ErrorIs(expected, actual error, msg ...interface{}) { +func (a Asserter) ErrorIs(expected, actual error, msg ...any) { a.TB.Helper() if errors.Is(actual, expected) { diff --git a/assert/Asserter_test.go b/assert/Asserter_test.go index d07a02a..a365f97 100644 --- a/assert/Asserter_test.go +++ b/assert/Asserter_test.go @@ -957,6 +957,12 @@ func TestAsserter_ContainExactly_slice(t *testing.T) { Oth: []int{4, 2, 2, 4}, IsFailed: true, }, + { + Desc: "when slices has matching values, but the other slice has additional value as well", + Src: []string{"42", "24"}, + Oth: []string{"24", "42", "13"}, + IsFailed: true, + }, } { t.Run(tc.Desc, AssertContainExactlyTestCase(tc.Src, tc.Oth, tc.IsFailed)) } diff --git a/assert/example_test.go b/assert/example_test.go index c485f35..51d4c62 100644 --- a/assert/example_test.go +++ b/assert/example_test.go @@ -300,3 +300,100 @@ func ExampleAsserter_Equal_isEqualFunctionThatSupportsErrorReturning() { assert.Must(tb).Equal(expected, actual) // fails because the error returned from the IsEqual function. } + +func ExampleTrue() { + var tb testing.TB + assert.True(tb, true) // ok + assert.True(tb, false) // Fatal +} + +func ExampleFalse() { + var tb testing.TB + assert.False(tb, false) // ok + assert.False(tb, true) // Fatal +} + +func ExampleNil() { + var tb testing.TB + assert.Nil(tb, nil) // ok + assert.Nil(tb, errors.New("boom")) // Fatal +} + +func ExampleNotNil() { + var tb testing.TB + assert.NotNil(tb, errors.New("boom")) // ok + assert.NotNil(tb, nil) // Fatal +} + +func ExampleEmpty() { + var tb testing.TB + assert.Empty(tb, "") // ok + assert.Empty(tb, "oh no!") // Fatal +} + +func ExampleNotEmpty() { + var tb testing.TB + assert.NotEmpty(tb, "huh...") // ok + assert.NotEmpty(tb, "") // Fatal +} + +func ExamplePanic() { + var tb testing.TB + + panicValue := assert.Panic(tb, func() { panic("at the disco") }) // ok + assert.Equal(tb, "some expected panic value", panicValue) + + assert.Panic(tb, func() {}) // Fatal +} + +func ExampleNotPanic() { + var tb testing.TB + assert.NotPanic(tb, func() {}) // ok + assert.NotPanic(tb, func() { panic("oh no!") }) // Fatal +} + +func ExampleEqual() { + var tb testing.TB + assert.Equal(tb, "a", "a") + assert.Equal(tb, 42, 42) + assert.Equal(tb, []int{42}, []int{42}) + assert.Equal(tb, map[int]int{24: 42}, map[int]int{24: 42}) +} + +func ExampleNotEqual() { + var tb testing.TB + assert.NotEqual(tb, "a", "b") + assert.Equal(tb, 13, 42) +} + +func ExampleContain() { + var tb testing.TB + assert.Must(tb).Contain(tb, []int{1, 2, 3}, 3, "optional assertion explanation") + assert.Must(tb).Contain(tb, []int{1, 2, 3}, []int{1, 2}, "optional assertion explanation") + assert.Must(tb).Contain(tb, + map[string]int{"The Answer": 42, "oth": 13}, + map[string]int{"The Answer": 42}, + "optional assertion explanation") +} + +func ExampleNotContain() { + var tb testing.TB + assert.Must(tb).NotContain(tb, []int{1, 2, 3}, 42) + assert.Must(tb).NotContain(tb, []int{1, 2, 3}, []int{1, 2, 42}) + assert.Must(tb).NotContain(tb, + map[string]int{"The Answer": 42, "oth": 13}, + map[string]int{"The Answer": 41}) +} + +func ExampleContainExactly() { + var tb testing.TB + assert.ContainExactly(tb, []int{1, 2, 3}, []int{2, 3, 1}, "optional assertion explanation") // true + assert.ContainExactly(tb, []int{1, 2, 3}, []int{1, 42, 2}, "optional assertion explanation") // false +} + +func ExampleErrorIs() { + var tb testing.TB + actualErr := errors.New("boom") + assert.ErrorIs(tb, errors.New("boom"), actualErr) // passes for equality + assert.ErrorIs(tb, errors.New("boom"), fmt.Errorf("wrapped error: %w", actualErr)) // passes for wrapped errors +} diff --git a/assert/pkgfunc.go b/assert/pkgfunc.go new file mode 100644 index 0000000..c2cd91f --- /dev/null +++ b/assert/pkgfunc.go @@ -0,0 +1,61 @@ +package assert + +import ( + "testing" +) + +func True(tb testing.TB, v bool, msg ...any) { + Must(tb).True(v, msg...) +} + +func False(tb testing.TB, v bool, msg ...any) { + Must(tb).False(v, msg...) +} + +func Nil(tb testing.TB, v any, msg ...any) { + Must(tb).Nil(v, msg...) +} + +func NotNil(tb testing.TB, v any, msg ...any) { + Must(tb).NotNil(v, msg...) +} + +func Empty(tb testing.TB, v any, msg ...any) { + Must(tb).Empty(v, msg...) +} + +func NotEmpty(tb testing.TB, v any, msg ...any) { + Must(tb).NotEmpty(v, msg...) +} + +func Panic(tb testing.TB, blk func(), msg ...any) any { + return Must(tb).Panic(blk, msg...) +} + +func NotPanic(tb testing.TB, blk func(), msg ...any) { + Must(tb).NotPanic(blk, msg...) +} + +func Equal[T any](tb testing.TB, expected, actually T, msg ...any) { + Must(tb).Equal(expected, actually, msg...) +} + +func NotEqual[T any](tb testing.TB, expected, actually T, msg ...any) { + Must(tb).NotEqual(expected, actually, msg...) +} + +func Contain(tb testing.TB, haystack, needle any, msg ...any) { + Must(tb).Contain(haystack, needle, msg...) +} + +func NotContain(tb testing.TB, haystack, v any, msg ...any) { + Must(tb).NotContain(haystack, v, msg...) +} + +func ContainExactly[T any](tb testing.TB, expected, actual T, msg ...any) { + Must(tb).ContainExactly(expected, actual, msg...) +} + +func ErrorIs(tb testing.TB, expected, actual error, msg ...any) { + Must(tb).ErrorIs(expected, actual, msg...) +} diff --git a/assert/pkgfunc_test.go b/assert/pkgfunc_test.go new file mode 100644 index 0000000..2cf6585 --- /dev/null +++ b/assert/pkgfunc_test.go @@ -0,0 +1,257 @@ +package assert_test + +import ( + "errors" + "fmt" + "testing" + + "github.com/adamluzsi/testcase" + "github.com/adamluzsi/testcase/assert" + "github.com/adamluzsi/testcase/internal" +) + +func TestPublicFunctions(t *testing.T) { + type TestCase struct { + Desc string + Failed bool + Assert func(testing.TB) + } + + for _, tc := range []TestCase{ + // .True + { + Desc: ".True - happy", + Failed: false, + Assert: func(tb testing.TB) { + assert.True(tb, true) + }, + }, + { + Desc: ".True - rainy", + Failed: true, + Assert: func(tb testing.TB) { + assert.True(tb, false) + }, + }, + // .False + { + Desc: ".False - happy", + Failed: false, + Assert: func(tb testing.TB) { + assert.False(tb, false) + }, + }, + { + Desc: ".False - rainy", + Failed: true, + Assert: func(tb testing.TB) { + assert.False(tb, true) + }, + }, + // .Nil + { + Desc: ".Nil - happy", + Failed: false, + Assert: func(tb testing.TB) { + assert.Nil(tb, nil) + }, + }, + { + Desc: ".Nil - rainy", + Failed: true, + Assert: func(tb testing.TB) { + assert.Nil(tb, &TestCase{}) + }, + }, + // .NotNil + { + Desc: ".NotNil - happy", + Failed: false, + Assert: func(tb testing.TB) { + assert.NotNil(tb, &TestCase{}) + }, + }, + { + Desc: ".NotNil - rainy", + Failed: true, + Assert: func(tb testing.TB) { + assert.NotNil(tb, nil) + }, + }, + // .Empty + { + Desc: ".Empty - happy", + Failed: false, + Assert: func(tb testing.TB) { + assert.Empty(tb, []int{}) + }, + }, + { + Desc: ".Empty - rainy", + Failed: true, + Assert: func(tb testing.TB) { + assert.Empty(tb, []int{42}) + }, + }, + // .NotEmpty + { + Desc: ".NotEmpty - happy", + Failed: false, + Assert: func(tb testing.TB) { + assert.NotEmpty(tb, []int{42}) + }, + }, + { + Desc: ".NotEmpty - rainy", + Failed: true, + Assert: func(tb testing.TB) { + assert.NotEmpty(tb, []int{}) + }, + }, + // .Panic + { + Desc: ".Panic - happy", + Failed: false, + Assert: func(tb testing.TB) { + expected := "boom!" + actual := assert.Panic(tb, func() { panic(expected) }) + assert.Must(tb).Equal(expected, actual) + }, + }, + { + Desc: ".Panic - rainy", + Failed: true, + Assert: func(tb testing.TB) { + assert.Panic(tb, func() {}) + }, + }, + // .NotPanic + { + Desc: ".NotPanic - happy", + Failed: false, + Assert: func(tb testing.TB) { + assert.NotPanic(tb, func() {}) + }, + }, + { + Desc: ".NotPanic - rainy", + Failed: true, + Assert: func(tb testing.TB) { + assert.NotPanic(tb, func() { panic("boom!") }) + }, + }, + // .Equal + { + Desc: ".Equal - happy", + Failed: false, + Assert: func(tb testing.TB) { + assert.Equal(tb, 42, 42) + assert.Equal(tb, "42", "42") + assert.Equal(tb, []string{"42"}, []string{"42"}) + }, + }, + { + Desc: ".Equal - rainy", + Failed: true, + Assert: func(tb testing.TB) { + assert.Equal(tb, 42, 24) + }, + }, + // .NotEqual + { + Desc: ".NotEqual - happy", + Failed: false, + Assert: func(tb testing.TB) { + assert.NotEqual(tb, 42, 24) + assert.NotEqual(tb, "42", "24") + assert.NotEqual(tb, []string{"42"}, []string{"42", "24"}) + }, + }, + { + Desc: ".NotEqual - rainy", + Failed: true, + Assert: func(tb testing.TB) { + assert.NotEqual(tb, 42, 42) + }, + }, + // .Contain + { + Desc: ".Contain - happy", + Failed: false, + Assert: func(tb testing.TB) { + assert.Contain(tb, "The Answer is 42", "42") + assert.Contain(tb, []string{"42", "24"}, "42") + assert.Contain(tb, map[string]int{"The answer": 42, "Are you good?": 0}, map[string]int{"The answer": 42}) + }, + }, + { + Desc: ".Contain - rainy", + Failed: true, + Assert: func(tb testing.TB) { + assert.Contain(tb, "The Answer is 42", "422") + }, + }, + // .NotContain + { + Desc: ".NotContain - happy", + Failed: false, + Assert: func(tb testing.TB) { + assert.NotContain(tb, "The Answer is 42", "422") + assert.NotContain(tb, []string{"42", "24"}, "13") + assert.NotContain(tb, + map[string]int{"The answer": 42, "Are you good?": 0}, + map[string]int{"The answer to you": 42}) + }, + }, + { + Desc: ".NotContain - rainy", + Failed: true, + Assert: func(tb testing.TB) { + assert.NotContain(tb, "The Answer is 42", "42") + }, + }, + // .ContainExactly + { + Desc: ".ContainExactly - happy", + Failed: false, + Assert: func(tb testing.TB) { + assert.ContainExactly(tb, []string{"42", "24"}, []string{"24", "42"}) + assert.ContainExactly(tb, map[string]int{"a": 1, "b": 2}, map[string]int{"b": 2, "a": 1}) + }, + }, + { + Desc: ".ContainExactly - rainy", + Failed: true, + Assert: func(tb testing.TB) { + assert.ContainExactly(tb, []string{"42", "24"}, []string{"24", "42", "13"}) + }, + }, + // .ErrorIs + { + Desc: ".ErrorIs - happy", + Failed: false, + Assert: func(tb testing.TB) { + expected := errors.New("boom") + actual := fmt.Errorf("wrapped boom: %w", expected) + assert.ErrorIs(tb, expected, actual) + }, + }, + { + Desc: ".ErrorIs - rainy", + Failed: true, + Assert: func(tb testing.TB) { + expected := errors.New("boom") + actual := fmt.Errorf("wrapped boom: %v", expected) + assert.ErrorIs(tb, expected, actual) + }, + }, + } { + t.Run(tc.Desc, func(t *testing.T) { + stub := &testcase.StubTB{} + internal.Recover(func() { + tc.Assert(stub) + }) + assert.Must(t).Equal(tc.Failed, stub.IsFailed) + }) + } +}