diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 737aed6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -sudo: false -language: go -go: - - 1.9.x - - master -before_install: - - go get github.com/mattn/goveralls -script: - - $GOPATH/bin/goveralls -service=travis-ci -matrix: - allow_failures: - - go: master - fast_finish: true -notifications: - email: false diff --git a/README.md b/README.md index a820e4b..e1d29a3 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Expected: value equal to Composed with AllOf: ```go -then.AssertThat(t, "abcdef", is.AllOf(is.ValueContaining("abc"), is.LessThan("ghi"))) +then.AssertThat(t, "abcdef", is.AllOf(is.StringContaining("abc"), is.LessThan("ghi"))) ``` Asynchronous Matching (v1.0.8 onwards): @@ -60,12 +60,36 @@ then.WithinTenSeconds(t, func(eventually gocrest.TestingT) { then.AssertThat(eventually, by.Channelling(channelTwo), is.EqualTo("11").Reason("This is unreachable")) }) ``` +# v.1.1.0 - generics + +Changes all the matchers to use generics instead of reflection. Some still use a bit of reflection, e.g. TypeName etc. + +## Other major changes: + +* ValueContaining has been split into StringContaining, MapContaining, MapContainingValues, MapMatchingValues, ArrayContaining and ArrayMatching. +* No longer panics with unknown types, as types will fail at compile time. +Some idiosyncrasies with the generic types do exist, but this is language specific; + +* Map matchers generally need to know the type of the map key values explicitly or the compiler will complain, e.g. +``` +then.AssertThat(testing, map[string]bool{"hi": true, "bye": true}, has.AllKeys[string, bool]("hi", "bye")) +``` +* `has.Length()` is likewise pernickety about types being explicit, mainly because it works on both strings and arrays. It needs to know both the type of the array and the array/string type. Confused? me too. +* `is.LessThan()` and `is.GreaterThan()` (and by extension `is.GreaterThanOrEqualTo` and `is.LessThanOrEqualTo`) no longer work on complex types. This is because the complex types do not support the comparison operators (yet, somehow, they could be compared by reflection 🤷 ) + +See the matcher_test.go file for full usage. + # Matchers so far.. - is.EqualTo(x) - is.EqualToIgnoringWhitespace(string) - compares two strings without comparing their whitespace characters. - is.Nil() - value must be nil -- is.ValueContaining(expected) -- acts like containsAll +- is.StringContaining(expected) -- acts like containsAll +- is.MapContaining(expected) -- acts like containsAll +- is.MapContainingValues(expected) -- acts like containsAll +- is.MapMatchingValues(expected) -- acts like containsAll +- is.ArrayContaining(expected) -- acts like containsAll +- is.ArrayMatching(expected) -- acts like containsAll - is.Not(m *Matcher) -- logical not of matcher's result - is.MatchForPattern(regex string) -- a string regex expression - has.FunctionNamed(string x) - checks if an interface has a function (method) diff --git a/by/eventually.go b/by/eventually.go index b1c3243..197c1fc 100644 --- a/by/eventually.go +++ b/by/eventually.go @@ -3,18 +3,21 @@ package by import ( "bufio" "io" - "reflect" ) -func Channelling(actual interface{}) interface{} { - var selectCase = make([]reflect.SelectCase, 1) - selectCase[0].Dir = reflect.SelectRecv - selectCase[0].Chan = reflect.ValueOf(actual) - _, recv, _ := reflect.Select(selectCase) - return recv.Interface() +// Channelling channels any channel of type T and returns the value from the channel +func Channelling[T any](actual chan T) T { + return <-actual } -func Reading(actual io.Reader, len int) interface{} { + +// Reading peeks at the value of a reader by reading `len` bytes ahead. +func Reading(actual io.Reader, len int) []byte { reader := bufio.NewReader(actual.(io.Reader)) peek, _ := reader.Peek(len) return peek } + +// Calling calls the function passed and returns the value +func Calling[K any, T any](actual func(T) K, value T) K { + return actual(value) +} diff --git a/go.mod b/go.mod index cc84e1a..7ab3a38 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,9 @@ module github.com/corbym/gocrest -go 1.15 +go 1.18 + +require ( + golang.org/x/sys v0.4.0 // indirect + golang.org/x/tools v0.5.1-0.20230111220935-a7f7db3f17fc // indirect + golang.org/x/tools/cmd/cover v0.1.0-deprecated // indirect +) diff --git a/has/haseveryelement.go b/has/haseveryelement.go index 8c7e31b..967a4e1 100644 --- a/has/haseveryelement.go +++ b/has/haseveryelement.go @@ -2,51 +2,37 @@ package has import ( "fmt" - "reflect" - "github.com/corbym/gocrest" ) // EveryElement Checks whether the nth element of the array/slice matches the nth expectation passed -// Panics if the actual is not an array/slice -// Panics if the count of the expectations does not match the array's/slice's length -func EveryElement(expects ...*gocrest.Matcher) *gocrest.Matcher { - match := new(gocrest.Matcher) +func EveryElement[A any](expects ...*gocrest.Matcher[A]) *gocrest.Matcher[[]A] { + match := new(gocrest.Matcher[[]A]) match.Describe = fmt.Sprintf("elements to match %s", describe(expects, "and")) for _, e := range expects { match.AppendActual(e.Actual) } - match.Matches = func(actual interface{}) bool { - - actualValue := reflect.ValueOf(actual) - switch actualValue.Kind() { - case reflect.Array, reflect.Slice: + match.Matches = func(actual []A) bool { + if len(actual) != len(expects) { + return false + } - if actualValue.Len() != len(expects) { + for i := 0; i < len(actual); i++ { + result := expects[i].Matches(actual[i]) + if !result { return false } - - for i := 0; i < actualValue.Len(); i++ { - result := expects[i].Matches(actualValue.Index(i).Interface()) - - if !result { - return false - } - } - - return true - - default: - panic("cannot determine type of variadic actual, " + actualValue.String()) } + + return true } return match } -func describe(matchers []*gocrest.Matcher, conjunction string) string { +func describe[A any](matchers []*gocrest.Matcher[A], conjunction string) string { var description string for x := 0; x < len(matchers); x++ { description += fmt.Sprintf("[%v]:%v", x, matchers[x].Describe) diff --git a/has/hasfield.go b/has/hasfield.go index a8e8ca7..cf4943f 100644 --- a/has/hasfield.go +++ b/has/hasfield.go @@ -8,10 +8,10 @@ import ( // FieldNamed is a naive implementation for testing if a struct has a particular field name. Does not check type. // Returns a matcher that will use reflect to check if the actual has the method given by expected -func FieldNamed(expected string) *gocrest.Matcher { - matcher := new(gocrest.Matcher) +func FieldNamed[A any](expected string) *gocrest.Matcher[A] { + matcher := new(gocrest.Matcher[A]) matcher.Describe = fmt.Sprintf("struct with function %s", expected) - matcher.Matches = func(actual interface{}) bool { + matcher.Matches = func(actual A) bool { typeOfActual := reflect.TypeOf(actual) matcher.Actual = fieldStringValue(typeOfActual) expectedName := reflect.ValueOf(expected).String() diff --git a/has/hasfunction.go b/has/hasfunction.go index df0e50d..6ec751e 100644 --- a/has/hasfunction.go +++ b/has/hasfunction.go @@ -8,10 +8,10 @@ import ( // FunctionNamed implementation for testing if a Type has a particular method name. Does not check parameters. // Returns a matcher that will use reflect to check if the actual has the method given by expected. -func FunctionNamed(expected string) *gocrest.Matcher { - matcher := new(gocrest.Matcher) +func FunctionNamed[A any](expected string) *gocrest.Matcher[A] { + matcher := new(gocrest.Matcher[A]) matcher.Describe = fmt.Sprintf("interface with function %s", expected) - matcher.Matches = func(actual interface{}) bool { + matcher.Matches = func(actual A) bool { typeOfActual := reflect.TypeOf(actual) matcher.Actual = actualStringValue(typeOfActual) expectedName := reflect.ValueOf(expected).String() diff --git a/has/haskey.go b/has/haskey.go index 21b7e88..27c9cef 100644 --- a/has/haskey.go +++ b/has/haskey.go @@ -3,31 +3,27 @@ package has import ( "fmt" "github.com/corbym/gocrest" - "reflect" ) // Key is a matcher that checks if actual has a key == expected. -// Panics when actual's Kind is not a map. // Returns a matcher that matches when a map has key == expected -func Key(expected interface{}) *gocrest.Matcher { - matcher := new(gocrest.Matcher) - matcher.Describe = fmt.Sprintf("map has key '%s'", expected) - matcher.Matches = func(actual interface{}) bool { +func Key[K comparable, V any](expected K) *gocrest.Matcher[map[K]V] { + matcher := new(gocrest.Matcher[map[K]V]) + matcher.Describe = fmt.Sprintf("map has key '%v'", expected) + matcher.Matches = func(actual map[K]V) bool { return hasKey(actual, expected) } return matcher } // AllKeys is a matcher that checks if map actual has all keys == expecteds. -// Panics when actual's Kind is not a map. // Returns a matcher that matches when a map has all keys == all expected. -func AllKeys(expected ...interface{}) *gocrest.Matcher { - matcher := new(gocrest.Matcher) - matcher.Describe = fmt.Sprintf("map has keys '%s'", expected) - matcher.Matches = func(actual interface{}) bool { - keyValuesToMatch := reflect.ValueOf(correctExpectedValue(expected...)) - for x := 0; x < keyValuesToMatch.Len(); x++ { - if !hasKey(actual, keyValuesToMatch.Index(x).Interface()) { +func AllKeys[K comparable, V any](expected ...K) *gocrest.Matcher[map[K]V] { + matcher := new(gocrest.Matcher[map[K]V]) + matcher.Describe = fmt.Sprintf("map has keys '%v'", expected) + matcher.Matches = func(actual map[K]V) bool { + for _, k := range expected { + if !hasKey(actual, k) { return false } } @@ -35,18 +31,10 @@ func AllKeys(expected ...interface{}) *gocrest.Matcher { } return matcher } -func correctExpectedValue(expected ...interface{}) interface{} { - kind := reflect.ValueOf(expected[0]).Kind() - if kind == reflect.Slice { - return expected[0] - } - return expected -} -func hasKey(actual interface{}, expected interface{}) bool { - mapKeys := reflect.ValueOf(actual).MapKeys() - for x := 0; x < len(mapKeys); x++ { - if mapKeys[x].Interface() == expected { +func hasKey[K comparable, V any](actual map[K]V, expected K) bool { + for k := range actual { + if k == expected { return true } } diff --git a/has/haslength.go b/has/haslength.go index 976f9c8..d5fd30a 100644 --- a/has/haslength.go +++ b/has/haslength.go @@ -3,29 +3,62 @@ package has import ( "fmt" "github.com/corbym/gocrest" - "reflect" ) -// Length can be called with arrays, maps, *gocrest.Matcher and strings but not numeric types. -// has.Length(is.GreaterThan(x)) is a valid call. +// Length can be called with arrays and strings // Returns a matcher that matches if the length matches the given criteria -func Length(expected interface{}) *gocrest.Matcher { +func Length[V any, A []V | string](expected int) *gocrest.Matcher[A] { const description = "value with length %v" - matcher := new(gocrest.Matcher) + matcher := new(gocrest.Matcher[A]) matcher.Describe = fmt.Sprintf(description, expected) - matcher.Matches = func(actual interface{}) bool { - if actual == nil { - return false - } - lenOfActual := reflect.ValueOf(actual).Len() + matcher.Matches = func(actual A) bool { + lenOfActual := len(actual) matcher.Actual = fmt.Sprintf("length was %d", lenOfActual) - switch expected.(type) { - case *gocrest.Matcher: - m := expected.(*gocrest.Matcher) - matcher.Describe = fmt.Sprintf(description, m.Describe) - return m.Matches(lenOfActual) - } return lenOfActual == expected } return matcher } + +// MapLength can be called with maps +// Returns a matcher that matches if the length matches the given criteria +func MapLength[K comparable, V any](expected int) *gocrest.Matcher[map[K]V] { + const description = "value with length %v" + matcher := new(gocrest.Matcher[map[K]V]) + matcher.Describe = fmt.Sprintf(description, expected) + matcher.Matches = func(actual map[K]V) bool { + lenOfActual := len(actual) + matcher.Actual = fmt.Sprintf("length was %d", lenOfActual) + return lenOfActual == expected + } + return matcher +} + +// LengthMatching can be called with arrays or strings +// Returns a matcher that matches if the length matches matcher passed in +func LengthMatching[V any, A []V | string](expected *gocrest.Matcher[int]) *gocrest.Matcher[A] { + const description = "value with length %v" + matcher := new(gocrest.Matcher[A]) + matcher.Describe = fmt.Sprintf(description, expected) + matcher.Matches = func(actual A) bool { + lenOfActual := len(actual) + matcher.Actual = fmt.Sprintf("length was %d", lenOfActual) + return expected.Matches(lenOfActual) + + } + return matcher +} + +// MapLengthMatching can be called with maps +// Returns a matcher that matches if the length matches the given matcher +func MapLengthMatching[K comparable, V any](expected *gocrest.Matcher[int]) *gocrest.Matcher[map[K]V] { + const description = "value with length %v" + matcher := new(gocrest.Matcher[map[K]V]) + matcher.Describe = fmt.Sprintf(description, expected) + matcher.Matches = func(actual map[K]V) bool { + lenOfActual := len(actual) + matcher.Actual = fmt.Sprintf("length was %d", lenOfActual) + return expected.Matches(lenOfActual) + + } + return matcher +} diff --git a/has/hasprefix.go b/has/hasprefix.go index 4dcf10b..77a21a1 100644 --- a/has/hasprefix.go +++ b/has/hasprefix.go @@ -7,14 +7,13 @@ import ( ) // Prefix returns a matcher that matches if the given string is prefixed with the expected string -// Function panics if the actual is not a string. // Uses strings.Prefix(act, exp) to evaluate strings. // Returns a matcher that returns true if the above conditions are met -func Prefix(expected string) *gocrest.Matcher { - matcher := new(gocrest.Matcher) +func Prefix(expected string) *gocrest.Matcher[string] { + matcher := new(gocrest.Matcher[string]) matcher.Describe = fmt.Sprintf("value with prefix %s", expected) - matcher.Matches = func(actual interface{}) bool { - return strings.HasPrefix(actual.(string), expected) + matcher.Matches = func(actual string) bool { + return strings.HasPrefix(actual, expected) } return matcher } diff --git a/has/hasstructvalues.go b/has/hasstructvalues.go index b544791..273851d 100644 --- a/has/hasstructvalues.go +++ b/has/hasstructvalues.go @@ -2,13 +2,12 @@ package has import ( "fmt" - "reflect" - "github.com/corbym/gocrest" + "reflect" ) -// StructMatchers Type that can be passed to StructWithValues. Mapp Struct field names to a matcher -type StructMatchers map[string]*gocrest.Matcher +// StructMatchers Type that can be passed to StructWithValues. Map Struct field names to a matcher +type StructMatchers[A any] map[string]*gocrest.Matcher[A] // StructWithValues Checks whether the actual struct matches all expectations passed as StructMatchers. // This method can be used to check single struct fields in different ways or omit checking some struct fields at all. @@ -16,15 +15,15 @@ type StructMatchers map[string]*gocrest.Matcher // Panics if the actual value is not a struct. // Panics if Structmatchers contains a key that can not be found in the actual struct. // Panics if Structmatchers contains a key that is unexported. -func StructWithValues(expects StructMatchers) *gocrest.Matcher { - match := new(gocrest.Matcher) +func StructWithValues[A any, B any](expects StructMatchers[B]) *gocrest.Matcher[A] { + match := new(gocrest.Matcher[A]) match.Describe = fmt.Sprintf("struct values to match {%s}", describeStructMatchers(expects)) for _, e := range expects { match.AppendActual(e.Actual) } - match.Matches = func(actual interface{}) bool { + match.Matches = func(actual A) bool { actualValue := reflect.ValueOf(actual) if actualValue.Kind() == reflect.Ptr { @@ -38,22 +37,23 @@ func StructWithValues(expects StructMatchers) *gocrest.Matcher { if !v.IsValid() { panic(fmt.Sprintf("Expect[%v] does not exist on actual struct", key)) } - - result := expect.Matches(v.Interface()) - - if !result { + var matches = false + if value, ok := v.Interface().(B); ok { + matches = expect.Matches(value) + } + if !matches { return false } } return true } - panic("cannot determine type of variadic actual, " + actualValue.String()) + panic("cannot determine type of actual, " + actualValue.String()) } return match } -func describeStructMatchers(matchers StructMatchers) string { +func describeStructMatchers[A any](matchers StructMatchers[A]) string { description := "" bindCount := 0 diff --git a/has/hassuffix.go b/has/hassuffix.go index c4bb926..6578e77 100644 --- a/has/hassuffix.go +++ b/has/hassuffix.go @@ -7,14 +7,13 @@ import ( ) // Suffix returns a matcher that matches if the given string is suffixed with the expected string. -// Panics if the actual is not a string. // Uses strings.HasSuffix(act,exp) to evaluate strings. // Returns a matcher that returns true if the above conditions are met. -func Suffix(expected string) *gocrest.Matcher { - matcher := new(gocrest.Matcher) +func Suffix(expected string) *gocrest.Matcher[string] { + matcher := new(gocrest.Matcher[string]) matcher.Describe = fmt.Sprintf("value with suffix %s", expected) - matcher.Matches = func(actual interface{}) bool { - return strings.HasSuffix(actual.(string), expected) + matcher.Matches = func(actual string) bool { + return strings.HasSuffix(actual, expected) } return matcher } diff --git a/has/hastypename.go b/has/hastypename.go index e13ce81..ff16773 100644 --- a/has/hastypename.go +++ b/has/hastypename.go @@ -5,25 +5,34 @@ import ( "reflect" ) -// TypeName returns true if the expected matches the actual Type's Name. Expected can be a matcher or a string. -// E.g. has.TypeName(EqualTo("pkg.Type")) would be true with instance of `type Type struct{}` in package name 'pkg'. -func TypeName(expected interface{}) *gocrest.Matcher { - matcher := new(gocrest.Matcher) - matcher.Matches = func(actual interface{}) bool { +// TypeName returns true if the expected matches the actual Type's Name. +// E.g. has.TypeName("pkg.Type") would be true with instance of `type Type struct{}` in package name 'pkg'. +func TypeName[A any](expected string) *gocrest.Matcher[A] { + matcher := new(gocrest.Matcher[A]) + matcher.Matches = func(actual A) bool { actualTypeName := reflect.TypeOf(actual).String() matcher.Actual = actualTypeName matcher.Describe = "has type " - switch expected.(type) { - case *gocrest.Matcher: - m := expected.(*gocrest.Matcher) - matches := m.Matches(actualTypeName) - matcher.AppendActual(m.Actual) - matcher.Describe += m.Describe - return matches - default: - matcher.Describe += "<" + expected.(string) + ">" - return actualTypeName == expected - } + matcher.Describe += "<" + expected + ">" + return actualTypeName == expected + + } + return matcher +} + +// TypeNameMatches returns true if the expected matches the actual Type's Name using the given matcher. +// E.g. has.TypeName(is.EqualTo("pkg.Type")) would be true with instance of `type Type struct{}` in package name 'pkg'. +func TypeNameMatches[A any](expected *gocrest.Matcher[string]) *gocrest.Matcher[A] { + matcher := new(gocrest.Matcher[A]) + matcher.Matches = func(actual A) bool { + actualTypeName := reflect.TypeOf(actual).String() + matcher.Actual = actualTypeName + matcher.Describe = "has type " + + matches := expected.Matches(actualTypeName) + matcher.AppendActual(expected.Actual) + matcher.Describe += expected.Describe + return matches } return matcher } diff --git a/is/allof.go b/is/allof.go index 4e67243..a466652 100644 --- a/is/allof.go +++ b/is/allof.go @@ -7,18 +7,18 @@ import ( // AllOf takes some matchers and checks if all the matchers return true. // Returns a matcher that performs the test on the input matchers. -func AllOf(allMatchers ...*gocrest.Matcher) *gocrest.Matcher { - matcher := new(gocrest.Matcher) +func AllOf[A any](allMatchers ...*gocrest.Matcher[A]) *gocrest.Matcher[A] { + matcher := new(gocrest.Matcher[A]) matcher.Describe = fmt.Sprintf("all of (%s)", describe("and", allMatchers)) matcher.Matches = matchAll(allMatchers, matcher) return matcher } -func matchAll(allMatchers []*gocrest.Matcher, allOf *gocrest.Matcher) func(actual interface{}) bool { - return func(actual interface{}) bool { +func matchAll[A any](allMatchers []*gocrest.Matcher[A], allOf *gocrest.Matcher[A]) func(actual A) bool { + return func(actual A) bool { allOf.AppendActual(fmt.Sprintf("actual <%v>", actual)) matches := true - var failingMatchers []*gocrest.Matcher + var failingMatchers []*gocrest.Matcher[A] for x := 0; x < len(allMatchers); x++ { if !allMatchers[x].Matches(actual) { matches = false diff --git a/is/anyof.go b/is/anyof.go index 770a3ad..d9b8d2a 100644 --- a/is/anyof.go +++ b/is/anyof.go @@ -7,15 +7,15 @@ import ( // AnyOf takes some matchers and checks if at least one of the matchers return true. // Returns a matcher that performs the test on the input matchers. -func AnyOf(allMatchers ...*gocrest.Matcher) *gocrest.Matcher { - matcher := new(gocrest.Matcher) +func AnyOf[A any](allMatchers ...*gocrest.Matcher[A]) *gocrest.Matcher[A] { + matcher := new(gocrest.Matcher[A]) matcher.Describe = fmt.Sprintf("any of (%s)", describe("or", allMatchers)) matcher.Matches = anyMatcherMatches(allMatchers, matcher) return matcher } -func anyMatcherMatches(allMatchers []*gocrest.Matcher, anyOf *gocrest.Matcher) func(actual interface{}) bool { - return func(actual interface{}) bool { +func anyMatcherMatches[A any](allMatchers []*gocrest.Matcher[A], anyOf *gocrest.Matcher[A]) func(actual A) bool { + return func(actual A) bool { matches := false anyOf.AppendActual(fmt.Sprintf("actual <%v>", actual)) for x := 0; x < len(allMatchers); x++ { diff --git a/is/contains.go b/is/contains.go index e8e26fe..0557178 100644 --- a/is/contains.go +++ b/is/contains.go @@ -3,124 +3,163 @@ package is import ( "fmt" "github.com/corbym/gocrest" - "reflect" "strings" ) -// ValueContaining finds if x is contained in y. +// StringContaining finds if all x's are contained as value in y. // Acts like "ContainsAll", all elements given must be present (or must match) in actual in the same order as the expected values. -// If "expected" is an array or slice, we assume that actual is the same type. -// assertThat([]T, has.ValueContaining(a,b,c)) is also valid if variadic a,b,c are all type T (or matchers of T). -// For maps, the expected must also be a map or a variadic of expected values (or value matchers) and matches when -// both maps contain all key,values in expected or all variadic values are equal (or matchers match) respectively. -// For string, behaves like strings.Contains. -// Will panic if types cannot be converted correctly. -// Returns the Matcher that returns true if found. -func ValueContaining(expected ...interface{}) *gocrest.Matcher { - match := new(gocrest.Matcher) - correctVariadicExpected := correctExpectedValue(expected...) - match.Describe = fmt.Sprintf("something that contains %v", descriptionFor(expected...)) - match.Matches = func(actual interface{}) bool { - expectedAsStr, expectedOk := expected[0].(string) - actualAsStr, actualOk := actual.(string) - if expectedOk && actualOk { - return strings.Contains(actualAsStr, expectedAsStr) - } - actualValue := reflect.ValueOf(actual) - expectedValue := reflect.ValueOf(correctVariadicExpected) - switch actualValue.Kind() { - case reflect.Array, reflect.Slice: - return listContains(expectedValue, actualValue) - case reflect.Map: - if expectedValue.Kind() == reflect.Array || expectedValue.Kind() == reflect.Slice { - return mapContainsList(expectedValue, actualValue) +func StringContaining(expected ...string) *gocrest.Matcher[string] { + match := new(gocrest.Matcher[string]) + match.Describe = fmt.Sprintf("something that contains %v", expected) + match.Matches = func(actual string) bool { + for i := 0; i < len(expected); i++ { + if !strings.Contains(actual, expected[i]) { + return false } - return mapContains(expectedValue, actualValue) - default: - panic("cannot determine type of variadic actual, " + actualValue.String()) } + return true } return match } -func mapContainsList(expected reflect.Value, mapValue reflect.Value) bool { - contains := make(map[interface{}]bool) - for i := 0; i < expected.Len(); i++ { - for _, key := range mapValue.MapKeys() { - itemValue := expected.Index(i).Interface() - typeMatcher, ok := itemValue.(*gocrest.Matcher) - actualValue := mapValue.MapIndex(key).Interface() - if ok { - if typeMatcher.Matches(actualValue) { - contains[itemValue] = true - } - } else { - if actualValue == itemValue { - contains[itemValue] = true - } - } +// MapContaining finds if all map[k] 's value V is contained as a value of actual[k] +// Acts like "ContainsAll", all elements given must be present in actual in the same order as the expected values. +func MapContaining[K comparable, V comparable](expected map[K]V) *gocrest.Matcher[map[K]V] { + match := new(gocrest.Matcher[map[K]V]) + match.Describe = fmt.Sprintf("something that contains %v", expected) + match.Matches = func(actual map[K]V) bool { + return mapActualContainsExpected(expected, actual) + } + return match +} + +// MapContainingValues finds if all values V is contained as a value of actual[k] +// Acts like "ContainsAll", all elements given must be present in actual in the same order as the expected values. +func MapContainingValues[K comparable, V comparable](expected ...V) *gocrest.Matcher[map[K]V] { + match := new(gocrest.Matcher[map[K]V]) + match.Describe = fmt.Sprintf("something that contains %v", expected) + match.Matches = func(actual map[K]V) bool { + return mapActualContainsExpectedValues(expected, actual) + } + return match +} + +// MapMatchingValues finds if all values V is match a value of actual[k] +// Acts like "ContainsAll", all elements given must match in actual in the same order as the expected values. +func MapMatchingValues[K comparable, V comparable](expected ...*gocrest.Matcher[V]) *gocrest.Matcher[map[K]V] { + match := new(gocrest.Matcher[map[K]V]) + match.Describe = descriptionForMatchers(expected) + match.Matches = func(actual map[K]V) bool { + return mapActualMatchesExpected(expected, actual) + } + return match +} + +func descriptionForMatchers[A any](expected []*gocrest.Matcher[A]) string { + var description = "" + for x := 0; x < len(expected); x++ { + description += expected[x].Describe + if x < len(expected)-1 { + description += " and " } } - return len(contains) == expected.Len() + return description } -func mapContains(expected reflect.Value, actual reflect.Value) bool { - expectedKeys := expected.MapKeys() +// ArrayContaining finds if all x's are contained in y. +// Acts like "ContainsAll", all elements given must be present in actual in the same order as the expected values. +func ArrayContaining[A comparable](expected ...A) *gocrest.Matcher[[]A] { + match := new(gocrest.Matcher[[]A]) + match.Describe = fmt.Sprintf("something that contains %v", descriptionFor(expected)) + match.Matches = func(actual []A) bool { + return listContains(expected, actual) + } + return match +} - contains := make(map[interface{}]bool) +// ArrayMatching finds if all x's are matched in y. +// Acts like "ContainsAll", all elements given must be present in actual in the same order as the expected values. +func ArrayMatching[A comparable](expected ...*gocrest.Matcher[A]) *gocrest.Matcher[[]A] { + match := new(gocrest.Matcher[[]A]) + match.Describe = fmt.Sprintf("something that contains %v", descriptionFor(expected)) + match.Matches = func(actual []A) bool { + return listMatches(expected, actual) + } + return match +} +func mapActualContainsExpected[K comparable, V comparable](expected map[K]V, actual map[K]V) bool { + expectedKeys := make([]K, 0, len(expected)) + for k := range expected { + expectedKeys = append(expectedKeys, k) + } + contains := make(map[V]bool) for i := 0; i < len(expectedKeys); i++ { - val := actual.MapIndex(expectedKeys[i]) - if val.IsValid() { - if val.Interface() == expected.MapIndex(expectedKeys[i]).Interface() { - contains[val] = true + val := actual[expectedKeys[i]] + if val == expected[expectedKeys[i]] { + contains[val] = true + } + } + return len(contains) == len(expectedKeys) +} +func mapActualContainsExpectedValues[K comparable, V comparable](expected []V, actual map[K]V) bool { + contains := make(map[V]bool) + for i := 0; i < len(expected); i++ { + for k, v := range actual { + if actual[k] == expected[i] { + contains[v] = true + break } } } - return len(contains) == len(expected.MapKeys()) + return len(contains) == len(expected) } - -func listContains(expectedValue reflect.Value, actualValue reflect.Value) bool { +func mapActualMatchesExpected[K comparable, V comparable, A map[K]V](expected []*gocrest.Matcher[V], actual A) bool { + actualKeys := make([]K, 0, len(actual)) + for k := range actual { + actualKeys = append(actualKeys, k) + } contains := make(map[interface{}]bool) - for i := 0; i < expectedValue.Len(); i++ { - for y := 0; y < actualValue.Len(); y++ { - exp := expectedValue.Index(i).Interface() - act := actualValue.Index(y).Interface() - typeMatcher, ok := exp.(*gocrest.Matcher) - if ok { - if typeMatcher.Matches(act) { - contains[act] = true - } - } else { - if exp == act { - contains[act] = true - } + for i := 0; i < len(expected); i++ { + for _, v := range actual { + if expected[i].Matches(v) { + contains[v] = true } } } - return len(contains) == expectedValue.Len() + return len(contains) == len(expected) } -func correctExpectedValue(expected ...interface{}) interface{} { - kind := reflect.ValueOf(expected[0]).Kind() - if kind == reflect.Slice || kind == reflect.Map { - return expected[0] +func listContains[T comparable, A []T](expected A, actualValue A) bool { + contains := make(map[interface{}]bool) + for i := 0; i < len(expected); i++ { + for y := 0; y < len(actualValue); y++ { + exp := expected[i] + act := actualValue[y] + if exp == act { + contains[act] = true + } + } } - return expected + return len(contains) == len(expected) } - -func descriptionFor(expected ...interface{}) interface{} { - kind := reflect.ValueOf(expected[0]).Kind() - if kind == reflect.Slice || kind == reflect.Map { - return expected[0] +func listMatches[T comparable](expected []*gocrest.Matcher[T], actualValue []T) bool { + contains := make(map[interface{}]bool) + for i := 0; i < len(expected); i++ { + for y := 0; y < len(actualValue); y++ { + exp := expected[i] + act := actualValue[y] + if exp.Matches(act) { + contains[act] = true + } + } } + return len(contains) == len(expected) +} + +func descriptionFor[T any, A []T](expected A) string { var description = "" for x := 0; x < len(expected); x++ { - var matcher, ok = expected[x].(*gocrest.Matcher) - if ok { - description += matcher.Describe - } else { - description += fmt.Sprintf("%s", "<"+expected[x].(string)+">") - } + description += fmt.Sprintf("<%v>", expected[x]) if x < len(expected)-1 { description += " and " } diff --git a/is/describe.go b/is/describe.go index ee263a7..3aead55 100644 --- a/is/describe.go +++ b/is/describe.go @@ -5,7 +5,7 @@ import ( "github.com/corbym/gocrest" ) -func describe(conjunction string, matchers []*gocrest.Matcher) string { +func describe[A any](conjunction string, matchers []*gocrest.Matcher[A]) string { var description string for x := 0; x < len(matchers); x++ { description += matchers[x].Describe diff --git a/is/empty.go b/is/empty.go index b445f7b..2f8ce2f 100644 --- a/is/empty.go +++ b/is/empty.go @@ -2,28 +2,16 @@ package is import ( "github.com/corbym/gocrest" - "reflect" ) // Empty matches if the actual is "empty". // 'string' values are empty if they are "", maps, arrays and slices are empty if len(actual) is 0. -// Pointers and interfaces are empty when nil. -// Other types (int, float, bool) will cause the function to panic. // Returns a matcher that evaluates true if actual is "empty". -func Empty() *gocrest.Matcher { - matcher := new(gocrest.Matcher) +func Empty[K comparable, T any, A string | []T | map[K]T]() *gocrest.Matcher[A] { + matcher := new(gocrest.Matcher[A]) matcher.Describe = "empty value" - matcher.Matches = func(actual interface{}) bool { - if actual == nil { - return true - } - if actualValue, ok := actual.(string); ok { - return actualValue == "" - } - if reflect.ValueOf(actual).Len() == 0 { - return true - } - return false + matcher.Matches = func(actual A) bool { + return len(actual) == 0 } return matcher } diff --git a/is/equalto.go b/is/equalto.go index 4f90a31..caa3d78 100644 --- a/is/equalto.go +++ b/is/equalto.go @@ -1,28 +1,19 @@ package is import ( - "bytes" "fmt" "github.com/corbym/gocrest" "reflect" ) -// EqualTo checks if two values are equal. Uses DeepEqual (could be slow) or Compare for byte arrays. +// EqualTo checks if two values are equal. Uses DeepEqual (could be slow). // Like DeepEquals, if the types are not the same the matcher returns false. // Returns a matcher that will return true if two values are equal. -func EqualTo(expected interface{}) *gocrest.Matcher { - match := new(gocrest.Matcher) +func EqualTo[A any](expected A) *gocrest.Matcher[A] { + match := new(gocrest.Matcher[A]) match.Describe = fmt.Sprintf("value equal to <%v>", expected) - match.Matches = func(actual interface{}) bool { - switch actual.(type) { - case []byte: - expectedBytes := expected.([]byte) - actualBytes := actual.([]byte) - compare := bytes.Compare(expectedBytes, actualBytes) - return compare == 0 - default: - return reflect.DeepEqual(expected, actual) - } + match.Matches = func(actual A) bool { + return reflect.DeepEqual(expected, actual) } return match diff --git a/is/equaltoignoringwhitespace.go b/is/equaltoignoringwhitespace.go index 9713e79..0f88607 100644 --- a/is/equaltoignoringwhitespace.go +++ b/is/equaltoignoringwhitespace.go @@ -12,12 +12,11 @@ import ( // "a b c" is EqualToIgnoringWhitespace when compared with "a \nb \tc" // "ab\tc" is EqualToIgnoringWhitespace when compared with "a \nb \tc" // .. and so on. -func EqualToIgnoringWhitespace(expected string) (matcher *gocrest.Matcher) { - matcher = new(gocrest.Matcher) - matcher.Matches = func(actual interface{}) bool { - actualString := actual.(string) +func EqualToIgnoringWhitespace(expected string) (matcher *gocrest.Matcher[string]) { + matcher = new(gocrest.Matcher[string]) + matcher.Matches = func(actual string) bool { expectedFields := strings.Join(strings.Fields(expected), "") - actualFields := strings.Join(strings.Fields(actualString), "") + actualFields := strings.Join(strings.Fields(actual), "") equalToMatcher := EqualTo(expectedFields) matcher.Describe = "ignoring whitespace value equal to <" + expected + ">" diff --git a/is/greaterthan.go b/is/greaterthan.go index 5d3046b..3f97fea 100644 --- a/is/greaterthan.go +++ b/is/greaterthan.go @@ -3,37 +3,16 @@ package is import ( "fmt" "github.com/corbym/gocrest" - "reflect" ) // GreaterThan matcher compares two values that are numeric or string values, and when // called returns true if actual > expected. Strings are compared lexicographically with '>'. -// The matcher will always return false for unknown types. -// Actual and expected types must be the same underlying type, or the function will panic. // Returns a matcher that checks if actual is greater than expected. -func GreaterThan(expected interface{}) *gocrest.Matcher { - matcher := new(gocrest.Matcher) +func GreaterThan[A int | int8 | int16 | int32 | int64 | float32 | float64 | uint | uint16 | uint32 | uint64 | string](expected A) *gocrest.Matcher[A] { + matcher := new(gocrest.Matcher[A]) matcher.Describe = fmt.Sprintf("value greater than <%v>", expected) - matcher.Matches = func(actual interface{}) bool { - actualValue := reflect.ValueOf(actual) - expectedValue := reflect.ValueOf(expected) - switch expected.(type) { - case float32, float64: - { - return actualValue.Float() > expectedValue.Float() - } - case int, int8, int16, int32, int64: - { - return actualValue.Int() > expectedValue.Int() - } - case uint, uint8, uint16, uint32, uint64: - { - return actualValue.Uint() > expectedValue.Uint() - } - case string: - return actualValue.String() > expectedValue.String() - } - return false + matcher.Matches = func(actual A) bool { + return actual > expected } return matcher } diff --git a/is/greaterthanorequalto.go b/is/greaterthanorequalto.go index 23cc501..17f2018 100644 --- a/is/greaterthanorequalto.go +++ b/is/greaterthanorequalto.go @@ -2,11 +2,11 @@ package is import "github.com/corbym/gocrest" -// GreaterThanOrEqualTo is a short hand matcher for anyOf(greaterThan(x), equalTo(x)) +// GreaterThanOrEqualTo is a shorthand matcher for anyOf(greaterThan(x), equalTo(x)) // Returns a matcher matching if actual >= expected (using deepEquals). -func GreaterThanOrEqualTo(expected interface{}) *gocrest.Matcher { - matcher := new(gocrest.Matcher) - matcher.Matches = func(actual interface{}) bool { +func GreaterThanOrEqualTo[A int | int8 | int16 | int32 | int64 | float32 | float64 | uint | uint16 | uint32 | uint64 | string](expected A) *gocrest.Matcher[A] { + matcher := new(gocrest.Matcher[A]) + matcher.Matches = func(actual A) bool { anyOf := AnyOf(GreaterThan(expected), EqualTo(expected)) anyOfMatches := anyOf.Matches(actual) matcher.Describe = anyOf.Describe diff --git a/is/isfalse.go b/is/isfalse.go index 2095abb..06069e4 100644 --- a/is/isfalse.go +++ b/is/isfalse.go @@ -2,11 +2,11 @@ package is import "github.com/corbym/gocrest" -// False returns true if the actual matches false. Confusing but true. -func False() *gocrest.Matcher { - return &gocrest.Matcher{ +// False returns true if the actual equals false. Confusing but true. +func False() *gocrest.Matcher[bool] { + return &gocrest.Matcher[bool]{ Describe: "is false", - Matches: func(actual interface{}) bool { + Matches: func(actual bool) bool { return actual == false }, } diff --git a/is/istrue.go b/is/istrue.go index 3627bcf..d523b01 100644 --- a/is/istrue.go +++ b/is/istrue.go @@ -3,10 +3,10 @@ package is import "github.com/corbym/gocrest" // True returns true if the actual matches true -func True() *gocrest.Matcher { - return &gocrest.Matcher{ +func True[A bool]() *gocrest.Matcher[A] { + return &gocrest.Matcher[A]{ Describe: "is true", - Matches: func(actual interface{}) bool { + Matches: func(actual A) bool { return actual == true }, } diff --git a/is/lessthan.go b/is/lessthan.go index 2e55ac1..3c8f9e2 100644 --- a/is/lessthan.go +++ b/is/lessthan.go @@ -3,31 +3,16 @@ package is import ( "fmt" "github.com/corbym/gocrest" - "reflect" ) // LessThan matcher compares two values that are numeric or string values, and when // called returns true if actual < expected. Strings are compared lexicographically with '<'. -// The matcher will always return false for unknown types. -// Actual and expected types must be the same underlying type, or the function will panic. // Returns a matcher that checks if actual is greater than expected. -func LessThan(expected interface{}) *gocrest.Matcher { - matcher := new(gocrest.Matcher) +func LessThan[A int | int8 | int16 | int32 | int64 | float32 | float64 | uint | uint16 | uint32 | uint64 | string](expected A) *gocrest.Matcher[A] { + matcher := new(gocrest.Matcher[A]) matcher.Describe = fmt.Sprintf("value less than <%v>", expected) - matcher.Matches = func(actual interface{}) bool { - actualValue := reflect.ValueOf(actual) - expectedValue := reflect.ValueOf(expected) - switch expected.(type) { - case float32, float64: - return actualValue.Float() < expectedValue.Float() - case int, int8, int16, int32, int64: - return actualValue.Int() < expectedValue.Int() - case uint, uint8, uint16, uint32, uint64: - return actualValue.Uint() < expectedValue.Uint() - case string: - return actualValue.String() < expectedValue.String() - } - return false + matcher.Matches = func(actual A) bool { + return actual < expected } return matcher } diff --git a/is/lessthanorequalto.go b/is/lessthanorequalto.go index aa62321..f9c66dc 100644 --- a/is/lessthanorequalto.go +++ b/is/lessthanorequalto.go @@ -4,9 +4,9 @@ import "github.com/corbym/gocrest" // LessThanOrEqualTo is a short hand matcher for anyOf(lessThan(x), equalTo(x)) // Returns a matcher matching if actual <= expected (using deepEquals). -func LessThanOrEqualTo(expected interface{}) *gocrest.Matcher { - matcher := new(gocrest.Matcher) - matcher.Matches = func(actual interface{}) bool { +func LessThanOrEqualTo[A int | int8 | int16 | int32 | int64 | float32 | float64 | uint | uint16 | uint32 | uint64 | string](expected A) *gocrest.Matcher[A] { + matcher := new(gocrest.Matcher[A]) + matcher.Matches = func(actual A) bool { anyOf := AnyOf(LessThan(expected), EqualTo(expected)) anyOfMatches := anyOf.Matches(actual) matcher.Describe = anyOf.Describe diff --git a/is/matchespattern.go b/is/matchespattern.go index 70dd2cd..245a739 100644 --- a/is/matchespattern.go +++ b/is/matchespattern.go @@ -9,16 +9,16 @@ import ( // MatchForPattern matches if actual string matches the expected regex // String provided must be a valid for compilation with regexp.Compile. // Returns a matcher that uses the expected for a regex to match the actual value. -func MatchForPattern(expected string) *gocrest.Matcher { - matcher := new(gocrest.Matcher) +func MatchForPattern(expected string) *gocrest.Matcher[string] { + matcher := new(gocrest.Matcher[string]) matcher.Describe = fmt.Sprintf("a value that matches pattern %s", expected) - matcher.Matches = func(actual interface{}) bool { + matcher.Matches = func(actual string) bool { compiledExp, err := regexp.Compile(expected) if err != nil { matcher.Describe = err.Error() return false } - return compiledExp.MatchString(actual.(string)) + return compiledExp.MatchString(actual) } return matcher } diff --git a/is/nil.go b/is/nil.go index 123a27e..04a6e69 100644 --- a/is/nil.go +++ b/is/nil.go @@ -7,12 +7,12 @@ import ( ) // Nil matches if the actual value is nil -func Nil() *gocrest.Matcher { - match := new(gocrest.Matcher) +func Nil[A any]() *gocrest.Matcher[A] { + match := new(gocrest.Matcher[A]) match.Describe = "value that is " - match.Matches = func(actual interface{}) bool { + match.Matches = func(actual A) bool { match.Actual = fmt.Sprintf("%v", actual) - if actual == nil { + if any(actual) == nil { return true } switch reflect.TypeOf(actual).Kind() { diff --git a/is/not.go b/is/not.go index 7e2349e..b78ebef 100644 --- a/is/not.go +++ b/is/not.go @@ -6,10 +6,10 @@ import ( // Not negates the given matcher. // Returns a matcher that returns logical not of the matcher given. -func Not(matcher *gocrest.Matcher) *gocrest.Matcher { - match := new(gocrest.Matcher) +func Not[A any](matcher *gocrest.Matcher[A]) *gocrest.Matcher[A] { + match := new(gocrest.Matcher[A]) match.Describe = "not(" + matcher.Describe + ")" - match.Matches = func(actual interface{}) bool { + match.Matches = func(actual A) bool { matches := !matcher.Matches(actual) match.Actual = matcher.Actual return matches diff --git a/matcher.go b/matcher.go index 65b52c5..8771bda 100644 --- a/matcher.go +++ b/matcher.go @@ -6,9 +6,9 @@ import ( ) // Matcher provides the structure for matcher operations. -type Matcher struct { +type Matcher[A any] struct { // Matches returns true if the function matches. - Matches func(actual interface{}) bool + Matches func(actual A) bool // Describe describes the matcher (e.g. "a value EqualTo(foo)" - foo being the expected value) Describe string // Actual is used by then.AssertThat if the matcher @@ -21,24 +21,24 @@ type Matcher struct { } // Reason for the mismatch. -func (matcher *Matcher) Reason(r string) *Matcher { +func (matcher *Matcher[A]) Reason(r string) *Matcher[A] { matcher.ReasonString = r return matcher } // Describes the Matcher to conform to the Stringer interface -func (matcher *Matcher) String() string { +func (matcher *Matcher[A]) String() string { return matcher.Describe } // Reasonf allows a formatted reason for the mismatch. -func (matcher *Matcher) Reasonf(format string, args ...interface{}) *Matcher { +func (matcher *Matcher[A]) Reasonf(format string, args ...interface{}) *Matcher[A] { return matcher.Reason(fmt.Sprintf(format, args...)) } // AppendActual appends an actual string to the matcher's actual description. This is useful if you want // to preserve sub-matchers actual values. See is.AllOf() matcher for an example. -func (matcher *Matcher) AppendActual(actualAsString string) { +func (matcher *Matcher[A]) AppendActual(actualAsString string) { matcher.Actual += " " + actualAsString matcher.Actual = strings.TrimSpace(matcher.Actual) } diff --git a/matcher_test.go b/matcher_test.go index 5811088..b074af6 100644 --- a/matcher_test.go +++ b/matcher_test.go @@ -20,22 +20,53 @@ func init() { stubTestingT = new(StubTestingT) } -func TestHasLengthMatchesOrNot(testing *testing.T) { +func TestEqualTo(t *testing.T) { + then.AssertThat(t, "foo", is.EqualTo("foo")) + then.AssertThat(t, 1, is.EqualTo(1)) + then.AssertThat(t, []string{"a"}, is.EqualTo([]string{"a"})) +} + +func TestMixedMatcher(t *testing.T) { + then.AssertThat(t, "abcdef", is.AllOf(is.StringContaining("abc"), is.LessThan("ghi"))) +} + +func TestNil(testing *testing.T) { + values := struct { + actual *string + }{ + actual: nil, + } + then.AssertThat(testing, values.actual, is.Nil[*string]()) +} +func TestHasLengthStringMatchesOrNot(testing *testing.T) { var hasLengthItems = []struct { - actual interface{} - expected interface{} + actual string + expected int shouldFail bool }{ - {actual: nil, expected: nil, shouldFail: true}, {actual: "", expected: 0, shouldFail: false}, {actual: "a", expected: 1, shouldFail: false}, {actual: "a", expected: 1, shouldFail: false}, {actual: "1", expected: 1, shouldFail: false}, - {actual: []string{}, expected: 0, shouldFail: false}, - {actual: []string{"foo"}, expected: 1, shouldFail: false}, - {actual: []string{"foo"}, expected: 2, shouldFail: true}, - {actual: []string{"foo", "bar"}, expected: 2, shouldFail: false}, - {actual: map[string]bool{"hello": true}, expected: 1, shouldFail: false}, + } + for _, test := range hasLengthItems { + stubTestingT = new(StubTestingT) + then.AssertThat(testing, "a", has.Length[string, string](1)) + then.AssertThat(stubTestingT, test.actual, has.Length[string, string](test.expected)) + if stubTestingT.HasFailed() != test.shouldFail { + testing.Errorf("assertThat(%v, has.Length(%v)) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.expected, test.shouldFail, stubTestingT.HasFailed()) + } + } +} +func TestMapHasLengthOne(t *testing.T) { + then.AssertThat(t, map[string]bool{"hello": true}, has.MapLength[string, bool](1)) +} +func TestHasLengthMapMatchesOrNot(testing *testing.T) { + var hasLengthItems = []struct { + actual map[string]bool + expected *gocrest.Matcher[int] + shouldFail bool + }{ {actual: map[string]bool{"helloa": true}, expected: is.LessThan(1), shouldFail: true}, {actual: map[string]bool{"hellob": true}, expected: is.LessThanOrEqualTo(2), shouldFail: false}, {actual: map[string]bool{"helloc": true}, expected: is.GreaterThan(2), shouldFail: true}, @@ -43,7 +74,65 @@ func TestHasLengthMatchesOrNot(testing *testing.T) { } for _, test := range hasLengthItems { stubTestingT = new(StubTestingT) - then.AssertThat(stubTestingT, test.actual, has.Length(test.expected)) + then.AssertThat(stubTestingT, test.actual, has.MapLengthMatching[string, bool](test.expected)) + if stubTestingT.HasFailed() != test.shouldFail { + testing.Errorf("assertThat(%v, has.Length(%v)) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.expected, test.shouldFail, stubTestingT.HasFailed()) + } + } +} +func TestHasLengthArrayMatchesOrNot(testing *testing.T) { + var hasLengthItems = []struct { + actual []int + expected int + shouldFail bool + }{ + {actual: []int{}, expected: 0, shouldFail: false}, + {actual: []int{1}, expected: 1, shouldFail: false}, + {actual: []int{1}, expected: 2, shouldFail: true}, + {actual: []int{1, 2}, expected: 2, shouldFail: false}, + } + for _, test := range hasLengthItems { + stubTestingT = new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, has.Length[int, []int](test.expected)) + if stubTestingT.HasFailed() != test.shouldFail { + testing.Errorf("assertThat(%v, has.Length(%v)) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.expected, test.shouldFail, stubTestingT.HasFailed()) + } + } +} +func TestHasLengthMatchesArrayMatchesOrNot(testing *testing.T) { + var hasLengthItems = []struct { + actual []int + expected int + shouldFail bool + }{ + {actual: []int{}, expected: 0, shouldFail: false}, + {actual: []int{1}, expected: 1, shouldFail: false}, + {actual: []int{1}, expected: 2, shouldFail: true}, + {actual: []int{1, 2}, expected: 2, shouldFail: false}, + } + for _, test := range hasLengthItems { + stubTestingT = new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, has.LengthMatching[int, []int](is.EqualTo(test.expected))) + if stubTestingT.HasFailed() != test.shouldFail { + testing.Errorf("assertThat(%v, has.Length(%v)) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.expected, test.shouldFail, stubTestingT.HasFailed()) + } + } +} +func TestHasLengthArrayStringMatchesOrNot(testing *testing.T) { + var hasLengthItems = []struct { + actual []string + expected int + shouldFail bool + }{ + {actual: nil, expected: 2, shouldFail: true}, + {actual: []string{}, expected: 0, shouldFail: false}, + {actual: []string{"foo"}, expected: 1, shouldFail: false}, + {actual: []string{"foo"}, expected: 2, shouldFail: true}, + {actual: []string{"foo", "bar"}, expected: 2, shouldFail: false}, + } + for _, test := range hasLengthItems { + stubTestingT = new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, has.Length[string, []string](test.expected)) if stubTestingT.HasFailed() != test.shouldFail { testing.Errorf("assertThat(%v, has.Length(%v)) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.expected, test.shouldFail, stubTestingT.HasFailed()) } @@ -52,8 +141,8 @@ func TestHasLengthMatchesOrNot(testing *testing.T) { func TestAssertThatTwoValuesAreEqualOrNot(testing *testing.T) { var equalsItems = []struct { - actual interface{} - expected interface{} + actual any + expected any shouldFail bool }{ {actual: 1, expected: 1, shouldFail: false}, @@ -73,49 +162,73 @@ func TestAssertThatTwoValuesAreEqualOrNot(testing *testing.T) { func TestEmptyStringIsEmptyPasses(testing *testing.T) { var equalsItems = []struct { - actual interface{} + actual string shouldFail bool }{ {actual: "hi", shouldFail: true}, - {actual: nil, shouldFail: false}, {actual: "", shouldFail: false}, + } + for _, test := range equalsItems { + stubTestingT := new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, is.Empty[string, string, string]()) + if stubTestingT.HasFailed() != test.shouldFail { + testing.Errorf("assertThat(%v, Empty()) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.shouldFail, stubTestingT.HasFailed()) + } + } +} +func TestEmptyMapIsEmptyPasses(testing *testing.T) { + var equalsItems = []struct { + actual map[string]bool + shouldFail bool + }{ {actual: map[string]bool{"hello": true}, shouldFail: true}, {actual: map[string]bool{}, shouldFail: false}, + } + for _, test := range equalsItems { + stubTestingT := new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, is.Empty[string, bool, map[string]bool]()) + if stubTestingT.HasFailed() != test.shouldFail { + testing.Errorf("assertThat(%v, Empty()) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.shouldFail, stubTestingT.HasFailed()) + } + } +} +func TestEmptyArrayIsEmptyPasses(testing *testing.T) { + var equalsItems = []struct { + actual []string + shouldFail bool + }{ {actual: []string{}, shouldFail: false}, {actual: []string{"boo"}, shouldFail: true}, } for _, test := range equalsItems { stubTestingT := new(StubTestingT) - then.AssertThat(stubTestingT, test.actual, is.Empty()) + then.AssertThat(stubTestingT, test.actual, is.Empty[string, string, []string]()) if stubTestingT.HasFailed() != test.shouldFail { testing.Errorf("assertThat(%v, Empty()) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.shouldFail, stubTestingT.HasFailed()) } } } -func TestAssertThatTwoValuesAreGreaterThanOrNot(testing *testing.T) { +func TestAssertThatTwoIntValuesAreGreaterThanOrNotFails(testing *testing.T) { + then.AssertThat(stubTestingT, 1, is.GreaterThan(1)) + hasFailed(testing) + then.AssertThat(stubTestingT, 1.12, is.GreaterThan(1.12)) + hasFailed(testing) +} + +func hasFailed(testing *testing.T) { + if !stubTestingT.HasFailed() { + testing.Errorf(testing.Name()) + } +} +func TestAssertThatTwoValuesAreGreaterThanOrNotFails(testing *testing.T) { var equalsItems = []struct { - actual interface{} - expected interface{} + actual string + expected string shouldFail bool }{ - {actual: 1, expected: 1, shouldFail: true}, - {actual: 2, expected: 1, shouldFail: false}, - {actual: 1.12, expected: 1.12, shouldFail: true}, - {actual: float32(1.12), expected: float32(1.0), shouldFail: false}, - {actual: float64(1.24), expected: float64(1.0), shouldFail: false}, - {actual: uint(3), expected: uint(1), shouldFail: false}, - {actual: uint16(4), expected: uint16(1), shouldFail: false}, - {actual: uint32(6), expected: uint32(1), shouldFail: false}, - {actual: uint64(7), expected: uint64(1), shouldFail: false}, - {actual: uint64(8), expected: uint64(1), shouldFail: false}, - {actual: int16(9), expected: int16(1), shouldFail: false}, - {actual: int32(10), expected: int32(1), shouldFail: false}, - {actual: int64(11), expected: int64(1), shouldFail: false}, - {actual: int64(12), expected: int64(1), shouldFail: false}, {actual: "zzz", expected: "aaa", shouldFail: false}, {actual: "aaa", expected: "zzz", shouldFail: true}, - {actual: complex64(1.0), expected: complex64(1.0), shouldFail: true}, } for _, test := range equalsItems { stubTestingT = new(StubTestingT) @@ -125,73 +238,108 @@ func TestAssertThatTwoValuesAreGreaterThanOrNot(testing *testing.T) { } } } +func TestAssertThatTwoFloatValuesAreGreaterThanOrNot(testing *testing.T) { + then.AssertThat(testing, float32(1.12), is.GreaterThan(float32(1.0))) + then.AssertThat(testing, 2, is.GreaterThan(1)) + then.AssertThat(testing, 1.24, is.GreaterThan(1.0)) +} +func TestAssertThatTwoIntsValuesAreGreaterThanOrNot(testing *testing.T) { + then.AssertThat(testing, uint(3), is.GreaterThan(uint(1))) + then.AssertThat(testing, uint16(4), is.GreaterThan(uint16(1))) + then.AssertThat(testing, uint32(6), is.GreaterThan(uint32(1))) + then.AssertThat(testing, uint64(7), is.GreaterThan(uint64(1))) + then.AssertThat(testing, uint64(8), is.GreaterThan(uint64(1))) + then.AssertThat(testing, int16(9), is.GreaterThan(int16(1))) + then.AssertThat(testing, int32(10), is.GreaterThan(int32(1))) + then.AssertThat(testing, int64(11), is.GreaterThan(int64(1))) + then.AssertThat(testing, int64(12), is.GreaterThan(int64(1))) +} func TestAssertThatHasLengthFailsWithDescriptionTest(testing *testing.T) { - then.AssertThat(stubTestingT, "a", has.Length(2)) + then.AssertThat(stubTestingT, "a", has.Length[string, string](2)) if !strings.Contains(stubTestingT.MockTestOutput, "value with length 2") { testing.Errorf("did not get expected description, got: %s", stubTestingT.MockTestOutput) } } -func TestAssertThatTwoValuesAreGreaterThanOrEqualToOrNot(testing *testing.T) { +func TestAssertThatTwoIntsAreLessThanOrNot(testing *testing.T) { var equalsItems = []struct { - actual interface{} - expected interface{} + actual int + expected int shouldFail bool }{ - {actual: 1, expected: 1, shouldFail: false}, - {actual: 2, expected: 1, shouldFail: false}, - {actual: 1.12, expected: 1.12, shouldFail: false}, - {actual: 1.0, expected: 1.12, shouldFail: true}, - {actual: float32(1.12), expected: float32(1.0), shouldFail: false}, - {actual: float64(1.24), expected: float64(1.0), shouldFail: false}, - {actual: float64(0.5), expected: float64(1.0), shouldFail: true}, - {actual: uint(1), expected: uint(1), shouldFail: false}, - {actual: uint(3), expected: uint(1), shouldFail: false}, - {actual: uint16(4), expected: uint16(1), shouldFail: false}, - {actual: uint32(6), expected: uint32(1), shouldFail: false}, - {actual: uint64(7), expected: uint64(1), shouldFail: false}, - {actual: uint64(8), expected: uint64(1), shouldFail: false}, - {actual: int16(9), expected: int16(1), shouldFail: false}, - {actual: int32(10), expected: int32(1), shouldFail: false}, - {actual: int64(11), expected: int64(1), shouldFail: false}, - {actual: int64(12), expected: int64(1), shouldFail: false}, - {actual: "zzz", expected: "aaa", shouldFail: false}, - {actual: "aaa", expected: "zzz", shouldFail: true}, - {actual: "aaa", expected: "aaa", shouldFail: false}, - {actual: complex64(1.0), expected: complex64(1.0), shouldFail: false}, + {actual: 1, expected: 1, shouldFail: true}, + {actual: 1, expected: 2, shouldFail: false}, } for _, test := range equalsItems { stubTestingT = new(StubTestingT) - then.AssertThat(stubTestingT, test.actual, is.GreaterThanOrEqualTo(test.expected)) + then.AssertThat(stubTestingT, test.actual, is.LessThan(test.expected)) if stubTestingT.HasFailed() != test.shouldFail { - testing.Errorf("assertThat(%v, GreaterThan(%v)) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.expected, test.shouldFail, stubTestingT.HasFailed()) + testing.Errorf("assertThat(%v, LessThan(%v)) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.expected, test.shouldFail, stubTestingT.HasFailed()) } } } - -func TestAssertThatTwoValuesAreLessThanOrNot(testing *testing.T) { +func TestAssertThatTwoFloat32ValuesAreLessThanOrNot(testing *testing.T) { var equalsItems = []struct { - actual interface{} - expected interface{} + actual float32 + expected float32 shouldFail bool }{ - {actual: 1, expected: 1, shouldFail: true}, - {actual: 1, expected: 2, shouldFail: false}, {actual: 1.12, expected: 1.12, shouldFail: true}, {actual: float32(1.0), expected: float32(1.12), shouldFail: false}, - {actual: float64(1.0), expected: float64(1.24), shouldFail: false}, - {actual: uint(0), expected: uint(3), shouldFail: false}, + } + for _, test := range equalsItems { + stubTestingT = new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, is.LessThan(test.expected)) + if stubTestingT.HasFailed() != test.shouldFail { + testing.Errorf("assertThat(%v, LessThan(%v)) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.expected, test.shouldFail, stubTestingT.HasFailed()) + } + } +} +func TestAssertThatTwoFloat64sAreLessThanOrNot(testing *testing.T) { + var equalsItems = []struct { + actual float64 + expected float64 + shouldFail bool + }{ + {actual: 1.12, expected: 1.12, shouldFail: true}, + {actual: 1.0, expected: 1.24, shouldFail: false}, + } + for _, test := range equalsItems { + stubTestingT = new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, is.LessThan(test.expected)) + if stubTestingT.HasFailed() != test.shouldFail { + testing.Errorf("assertThat(%v, LessThan(%v)) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.expected, test.shouldFail, stubTestingT.HasFailed()) + } + } +} +func TestAssertThatTwoUintsAreLessThanOrNot(testing *testing.T) { + var equalsItems = []struct { + actual uint16 + expected uint16 + shouldFail bool + }{ + {actual: 1, expected: 1, shouldFail: true}, + {actual: 1, expected: 2, shouldFail: false}, {actual: uint16(1), expected: uint16(4), shouldFail: false}, - {actual: uint32(1), expected: uint32(6), shouldFail: false}, + } + for _, test := range equalsItems { + stubTestingT = new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, is.LessThan(test.expected)) + if stubTestingT.HasFailed() != test.shouldFail { + testing.Errorf("assertThat(%v, LessThan(%v)) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.expected, test.shouldFail, stubTestingT.HasFailed()) + } + } +} +func TestAssertThatTwoUints64sAreLessThanOrNot(testing *testing.T) { + var equalsItems = []struct { + actual uint64 + expected uint64 + shouldFail bool + }{ + {actual: 1, expected: 1, shouldFail: true}, + {actual: 1, expected: 2, shouldFail: false}, {actual: uint64(1), expected: uint64(7), shouldFail: false}, {actual: uint64(1), expected: uint64(8), shouldFail: false}, - {actual: int16(1), expected: int16(9), shouldFail: false}, - {actual: int32(1), expected: int32(10), shouldFail: false}, - {actual: int64(1), expected: int64(11), shouldFail: false}, - {actual: "aaa", expected: "zzz", shouldFail: false}, - {actual: "zzz", expected: "aaa", shouldFail: true}, - {actual: "aaa", expected: "aaa", shouldFail: true}, - {actual: complex64(1.0), expected: complex64(1.0), shouldFail: true}, // cannot compare complex types, so fails } for _, test := range equalsItems { stubTestingT = new(StubTestingT) @@ -202,32 +350,69 @@ func TestAssertThatTwoValuesAreLessThanOrNot(testing *testing.T) { } } -func TestAssertThatTwoValuesAreLessThanOrEqualToPassesOrNot(testing *testing.T) { +func TestAssertThatTwoIntValuesAreLessThanOrEqualToPassesOrNot(testing *testing.T) { var equalsItems = []struct { - actual interface{} - expected interface{} + actual int16 + expected int16 shouldFail bool }{ {actual: 1, expected: 1, shouldFail: false}, {actual: 1, expected: 2, shouldFail: false}, - {actual: 1.12, expected: 1.12, shouldFail: false}, - {actual: 2.3, expected: 1.12, shouldFail: true}, - {actual: float32(1.0), expected: float32(1.12), shouldFail: false}, - {actual: float64(1.0), expected: float64(1.24), shouldFail: false}, - {actual: float64(1.0), expected: float64(1.0), shouldFail: false}, - {actual: float64(1.0), expected: float64(0.5), shouldFail: true}, - {actual: uint(0), expected: uint(0), shouldFail: false}, - {actual: uint16(1), expected: uint16(4), shouldFail: false}, - {actual: uint32(1), expected: uint32(6), shouldFail: false}, - {actual: uint64(1), expected: uint64(7), shouldFail: false}, - {actual: uint64(1), expected: uint64(8), shouldFail: false}, {actual: int16(1), expected: int16(9), shouldFail: false}, + } + for _, test := range equalsItems { + stubTestingT = new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, is.LessThanOrEqualTo(test.expected)) + if stubTestingT.HasFailed() != test.shouldFail { + testing.Errorf("assertThat(%v, LessThan(%v)) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.expected, test.shouldFail, stubTestingT.HasFailed()) + } + } +} +func TestAssertThatTwoInt32ValuesAreLessThanOrEqualToPassesOrNot(testing *testing.T) { + var equalsItems = []struct { + actual int32 + expected int32 + shouldFail bool + }{ + {actual: 1, expected: 1, shouldFail: false}, + {actual: 1, expected: 2, shouldFail: false}, {actual: int32(1), expected: int32(10), shouldFail: false}, + } + for _, test := range equalsItems { + stubTestingT = new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, is.LessThanOrEqualTo(test.expected)) + if stubTestingT.HasFailed() != test.shouldFail { + testing.Errorf("assertThat(%v, LessThan(%v)) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.expected, test.shouldFail, stubTestingT.HasFailed()) + } + } +} +func TestAssertThatTwoInt64ValuesAreLessThanOrEqualToPassesOrNot(testing *testing.T) { + var equalsItems = []struct { + actual int64 + expected int64 + shouldFail bool + }{ + {actual: 1, expected: 1, shouldFail: false}, + {actual: 1, expected: 2, shouldFail: false}, {actual: int64(1), expected: int64(11), shouldFail: false}, + } + for _, test := range equalsItems { + stubTestingT = new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, is.LessThanOrEqualTo(test.expected)) + if stubTestingT.HasFailed() != test.shouldFail { + testing.Errorf("assertThat(%v, LessThan(%v)) gave unexpected test result (wanted failed %v, got failed %v)", test.actual, test.expected, test.shouldFail, stubTestingT.HasFailed()) + } + } +} +func TestAssertThatTwoStringValuesAreLessThanOrEqualToPassesOrNot(testing *testing.T) { + var equalsItems = []struct { + actual string + expected string + shouldFail bool + }{ {actual: "aaa", expected: "zzz", shouldFail: false}, {actual: "zzz", expected: "aaa", shouldFail: true}, {actual: "aaa", expected: "aaa", shouldFail: false}, - {actual: complex64(1.0), expected: complex64(1.0), shouldFail: false}, } for _, test := range equalsItems { stubTestingT = new(StubTestingT) @@ -246,32 +431,33 @@ func TestNotReturnsTheOppositeOfGivenMatcher(testing *testing.T) { } func TestNotReturnsTheSubMatcherActual(testing *testing.T) { - not := is.Not(has.Length(1)) + not := is.Not[string](has.Length[string, string](1)) not.Matches("a") then.AssertThat(testing, not.Actual, is.EqualTo("length was 1")) } func TestAnyofReturnsTheSubMatcherActual(testing *testing.T) { - anyOf := is.AnyOf(has.Length(1), is.EqualTo("a")) + anyOf := is.AnyOf[string](has.Length[string, string](1), is.EqualTo("a")) anyOf.Matches("a") then.AssertThat(testing, anyOf.Actual, is.EqualTo("actual length was 1")) } func TestAllofReturnsTheSubMatcherActual(testing *testing.T) { - anyOf := is.AllOf(has.Length(1), is.EqualTo("a")) + anyOf := is.AllOf[string](has.Length[string, string](1), is.EqualTo("a")) anyOf.Matches("a") then.AssertThat(testing, anyOf.Actual, is.EqualTo("actual length was 1")) } func TestIsNilMatches(testing *testing.T) { - then.AssertThat(testing, nil, is.Nil()) + then.AssertThat(testing, nil, is.Nil[any]()) } func TestIsNilFails(testing *testing.T) { - then.AssertThat(stubTestingT, 2, is.Nil()) + var actual = 2 + then.AssertThat(stubTestingT, &actual, is.Nil[*int]()) if !stubTestingT.HasFailed() { testing.Fail() } @@ -280,7 +466,7 @@ func TestIsNilFails(testing *testing.T) { func TestContainsFailsForTwoStringArraysTest(testing *testing.T) { actualList := []string{"Foo", "Bar"} expectedList := []string{"Baz", "Bing"} - then.AssertThat(stubTestingT, actualList, is.ValueContaining(expectedList)) + then.AssertThat(stubTestingT, actualList, is.ArrayContaining(expectedList...)) if !stubTestingT.HasFailed() { testing.Fail() } @@ -289,7 +475,7 @@ func TestContainsFailsForTwoStringArraysTest(testing *testing.T) { func TestContainsFailsForTwoIntArraysTest(testing *testing.T) { actualList := []int{12, 13} expectedList := []int{14, 15} - then.AssertThat(stubTestingT, actualList, is.ValueContaining(expectedList)) + then.AssertThat(stubTestingT, actualList, is.ArrayContaining(expectedList...)) if !stubTestingT.HasFailed() { testing.Fail() } @@ -298,13 +484,13 @@ func TestContainsFailsForTwoIntArraysTest(testing *testing.T) { func TestContainsForString(testing *testing.T) { actualList := []string{"Foo", "Bar"} expected := "Foo" - then.AssertThat(testing, actualList, is.ValueContaining(expected)) + then.AssertThat(testing, actualList, is.ArrayContaining(expected)) } func TestContainsFailsForString(testing *testing.T) { actualList := []string{"Foo", "Bar"} expected := "Moo" - then.AssertThat(stubTestingT, actualList, is.ValueContaining(expected)) + then.AssertThat(stubTestingT, actualList, is.ArrayContaining(expected)) if !stubTestingT.HasFailed() { testing.Fail() } @@ -313,23 +499,23 @@ func TestContainsFailsForString(testing *testing.T) { func TestContainsForSlice(testing *testing.T) { actualList := []string{"Foo", "Bar", "Bong", "Boom"} expected := []string{"Baz", "Bing", "Bong"} - then.AssertThat(testing, actualList[2:2], is.ValueContaining(expected[2:2])) + then.AssertThat(testing, actualList[2:2], is.ArrayContaining(expected[2:2]...)) } func TestContainsForList(testing *testing.T) { actualList := []string{"Foo", "Bar", "Bong", "Boom"} expected := []string{"Boom", "Bong", "Bar"} - then.AssertThat(testing, actualList, is.ValueContaining(expected)) + then.AssertThat(testing, actualList, is.ArrayContaining(expected...)) } func TestContainsForVariadic(testing *testing.T) { actualList := []string{"Foo", "Bar", "Bong", "Boom"} - then.AssertThat(testing, actualList, is.ValueContaining("Boom", "Bong", "Bar")) + then.AssertThat(testing, actualList, is.ArrayContaining("Boom", "Bong", "Bar")) } func TestContainsForVariadicMatchers(testing *testing.T) { actualList := []string{"Foo", "Bar", "Bong", "Boom"} - then.AssertThat(testing, actualList, is.ValueContaining(is.EqualTo("Boom"), has.Suffix("ng"), has.Prefix("Ba"))) + then.AssertThat(testing, actualList, is.ArrayMatching(is.EqualTo("Boom"), has.Suffix("ng"), has.Prefix("Ba"))) } func TestMapContainsMap(testing *testing.T) { @@ -341,7 +527,7 @@ func TestMapContainsMap(testing *testing.T) { "bing": "boop", } - then.AssertThat(testing, actualList, is.ValueContaining(expected)) + then.AssertThat(testing, actualList, is.MapContaining(expected)) } func TestMapContainsValues(testing *testing.T) { @@ -349,33 +535,21 @@ func TestMapContainsValues(testing *testing.T) { "bing": "boop", "bling": "bling", } - then.AssertThat(testing, actualList, is.ValueContaining("bling", "boop")) + then.AssertThat(testing, actualList, is.MapContainingValues[string]("boop", "bling")) } -func TestMapContainsValuesMatching(testing *testing.T) { +func TestMapMatchesValues(testing *testing.T) { actualList := map[string]string{ "bing": "boop", "bling": "bling", } - then.AssertThat(testing, actualList, is.ValueContaining(is.EqualTo("bling"), is.EqualTo("boop"))) + then.AssertThat(testing, actualList, is.MapMatchingValues[string](is.EqualTo("boop"), is.EqualTo("bling"))) } func TestStringContains_String(testing *testing.T) { actualList := "abcd" expected := "bc" - then.AssertThat(testing, actualList, is.ValueContaining(expected)) -} - -func TestValueContaining_PanicsWithUnknownType(testing *testing.T) { - defer func() { - recover := recover() - then.AssertThat(testing, recover, is.Not(is.Nil())) - }() - expected := "abcd" - actualList := make(chan string, 3) - actualList <- "abc" - - then.AssertThat(testing, actualList, is.ValueContaining(expected)) + then.AssertThat(testing, actualList, is.StringContaining(expected)) } func TestMatchesPatternMatchesString(testing *testing.T) { @@ -432,7 +606,7 @@ func TestHasFunctionPasses(testing *testing.T) { } actual := new(MyType) expected := "f" - then.AssertThat(testing, actual, has.FunctionNamed(expected)) + then.AssertThat(testing, actual, has.FunctionNamed[*MyType](expected)) } func TestHasFunctionDoesNotPass(testing *testing.T) { @@ -441,7 +615,7 @@ func TestHasFunctionDoesNotPass(testing *testing.T) { } actual := new(MyType) expected := "E" - then.AssertThat(stubTestingT, actual, has.FunctionNamed(expected)) + then.AssertThat(stubTestingT, actual, has.FunctionNamed[*MyType](expected)) if !stubTestingT.HasFailed() { testing.Fail() } @@ -453,7 +627,7 @@ func TestHasFieldNamedPasses(testing *testing.T) { } actual := new(T) expected := "f" - then.AssertThat(testing, actual, has.FieldNamed(expected)) + then.AssertThat(testing, actual, has.FieldNamed[*T](expected)) } func TestHasFieldDoesNotPass(testing *testing.T) { @@ -462,7 +636,7 @@ func TestHasFieldDoesNotPass(testing *testing.T) { } actual := new(T) expected := "E" - then.AssertThat(stubTestingT, actual, has.FieldNamed(expected)) + then.AssertThat(stubTestingT, actual, has.FieldNamed[*T](expected)) if !stubTestingT.HasFailed() { testing.Fail() } @@ -470,12 +644,12 @@ func TestHasFieldDoesNotPass(testing *testing.T) { func TestAllOfMatches(testing *testing.T) { actual := "abcdef" - then.AssertThat(testing, actual, is.AllOf(is.EqualTo("abcdef"), is.ValueContaining("e"))) + then.AssertThat(testing, actual, is.AllOf(is.EqualTo("abcdef"), is.StringContaining("e"))) } func TestAllOfFailsToMatch(testing *testing.T) { actual := "abc" - then.AssertThat(stubTestingT, actual, is.AllOf(is.EqualTo("abc"), is.ValueContaining("e"))) + then.AssertThat(stubTestingT, actual, is.AllOf(is.EqualTo("abc"), is.StringContaining("e"))) if !stubTestingT.HasFailed() { testing.Fail() } @@ -483,12 +657,12 @@ func TestAllOfFailsToMatch(testing *testing.T) { func TestAnyOfMatches(testing *testing.T) { actual := "abcdef" - then.AssertThat(testing, actual, is.AnyOf(is.EqualTo("abcdef"), is.ValueContaining("g"))) + then.AssertThat(testing, actual, is.AnyOf(is.EqualTo("abcdef"), is.StringContaining("g"))) } func TestAnyOfFailsToMatch(testing *testing.T) { actual := "abc" - then.AssertThat(stubTestingT, actual, is.AnyOf(is.EqualTo("efg"), is.ValueContaining("e"))) + then.AssertThat(stubTestingT, actual, is.AnyOf(is.EqualTo("efg"), is.StringContaining("e"))) if !stubTestingT.HasFailed() { testing.Fail() } @@ -498,17 +672,15 @@ func TestHasKeyMatches(testing *testing.T) { type T struct{} expectedT := new(T) var equalsItems = []struct { - actual interface{} - expected interface{} + actual map[*T]bool + expected *T shouldFail bool }{ - {actual: map[string]bool{"hi": true}, expected: "hi", shouldFail: false}, - {actual: map[*T]bool{expectedT: true}, expected: "hi", shouldFail: true}, {actual: map[*T]bool{expectedT: true}, expected: expectedT, shouldFail: false}, } for _, test := range equalsItems { stubTestingT := new(StubTestingT) - then.AssertThat(stubTestingT, test.actual, has.Key(test.expected)) + then.AssertThat(stubTestingT, test.actual, has.Key[*T, bool](test.expected)) if stubTestingT.HasFailed() != test.shouldFail { testing.Errorf("unexpected result HasKey: wanted fail was %v but failed %v", test.shouldFail, stubTestingT.HasFailed()) } @@ -516,62 +688,132 @@ func TestHasKeyMatches(testing *testing.T) { } func TestHasKeysMatches(testing *testing.T) { + then.AssertThat(testing, map[string]bool{"hi": true, "bye": true}, has.AllKeys[string, bool]("hi", "bye")) type T struct{} expectedT := new(T) secondExpectedT := new(T) + then.AssertThat(testing, map[*T]bool{expectedT: true, secondExpectedT: true}, has.AllKeys[*T, bool](expectedT, secondExpectedT)) +} + +func TestBoolMatcherDescription(t *testing.T) { var equalsItems = []struct { - actual interface{} - expected interface{} - shouldFail bool + description string + actual bool + matcher *gocrest.Matcher[bool] + expected string }{ - {actual: map[string]bool{"hi": true, "bye": true}, expected: []string{"hi", "bye"}, shouldFail: false}, - {actual: map[*T]bool{expectedT: true, secondExpectedT: true}, expected: []*T{expectedT, secondExpectedT}, shouldFail: false}, - {actual: map[*T]bool{expectedT: true}, expected: "foo", shouldFail: true}, + {description: "is true", actual: false, matcher: is.True(), expected: "is true"}, + {description: "is false", actual: true, matcher: is.False(), expected: "is false"}, } for _, test := range equalsItems { - stubTestingT := new(StubTestingT) - then.AssertThat(stubTestingT, test.actual, has.AllKeys(test.expected)) - if stubTestingT.HasFailed() != test.shouldFail { - testing.Errorf("unexpected result HasKeys(%v): wanted fail was %v but failed %v", test.actual, test.shouldFail, stubTestingT.HasFailed()) - } + t.Run(test.description, func(innerT *testing.T) { + stubTestingT := new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, test.matcher) + if !strings.Contains(stubTestingT.MockTestOutput, test.expected) { + innerT.Errorf("%s did not fail with expected desc <%s>, got: %s", test.description, test.expected, stubTestingT.MockTestOutput) + } + }) } } - -func TestHasKeysWithVariadic(testing *testing.T) { - actual := map[string]bool{"hi": true, "bye": false} - then.AssertThat(testing, actual, has.AllKeys("hi", "bye")) +func TestTypeNameMatcherDescription(t *testing.T) { + var equalsItems = []struct { + description string + actual *testing.T + matcher *gocrest.Matcher[*testing.T] + expected string + }{ + {description: "has type T", actual: t, matcher: has.TypeName[*testing.T]("string"), expected: "has type "}, + {description: "has type T matcher", actual: t, matcher: has.TypeNameMatches[*testing.T](is.EqualTo("string")), expected: "has type value equal to "}, + } + for _, test := range equalsItems { + t.Run(test.description, func(innerT *testing.T) { + stubTestingT := new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, test.matcher) + if !strings.Contains(stubTestingT.MockTestOutput, test.expected) { + innerT.Errorf("%s did not fail with expected desc <%s>, got: %s", test.description, test.expected, stubTestingT.MockTestOutput) + } + }) + } } - -func TestMatcherDescription(t *testing.T) { +func TestSizeMatcherDescription(t *testing.T) { var equalsItems = []struct { description string - actual interface{} - matcher *gocrest.Matcher + actual int + matcher *gocrest.Matcher[int] expected string }{ - {description: "is true", actual: false, matcher: is.True(), expected: "is true"}, - {description: "is false", actual: true, matcher: is.False(), expected: "is false"}, - {description: "has type T", actual: t, matcher: has.TypeName("string"), expected: "has type "}, - {description: "has type T matcher", actual: t, matcher: has.TypeName(is.EqualTo("string")), expected: "has type value equal to "}, {description: "EqualTo.Reasonf", actual: 1, matcher: is.EqualTo(2).Reasonf("arithmetic %s is wrong", "foo"), expected: "arithmetic foo is wrong"}, {description: "EqualTo.Reason", actual: 1, matcher: is.EqualTo(2).Reason("arithmetic is wrong"), expected: "arithmetic is wrong\nExpected: value equal to <2>\n but: <1>\n"}, {description: "Not", actual: 2, matcher: is.Not(is.EqualTo(2)), expected: "\nExpected: not(value equal to <2>)\n but: <2>\n"}, - {description: "Empty", actual: map[string]bool{"foo": true}, matcher: is.Empty(), expected: "empty value"}, {description: "GreaterThan", actual: 1, matcher: is.GreaterThan(2), expected: "value greater than <2>"}, {description: "GreaterThanOrEqual", actual: 1, matcher: is.GreaterThanOrEqualTo(2), expected: "any of (value greater than <2> or value equal to <2>)"}, {description: "LessThan", actual: 2, matcher: is.LessThan(1), expected: "value less than <1>"}, {description: "LessThanOrEqualTo", actual: 2, matcher: is.LessThanOrEqualTo(1), expected: "any of (value less than <1> or value equal to <1>)"}, - {description: "Nil", actual: 1, matcher: is.Nil(), expected: "value that is "}, - {description: "ValueContaining", actual: []string{"Foo", "Bar"}, matcher: is.ValueContaining([]string{"Baz", "Bing"}), expected: "something that contains [Baz Bing]"}, - {description: "ValueContaining", actual: []string{"Foo", "Bar"}, matcher: is.ValueContaining(is.EqualTo("Baz"), is.EqualTo("Bing")), expected: "something that contains value equal to and value equal to "}, + } + for _, test := range equalsItems { + t.Run(test.description, func(innerT *testing.T) { + stubTestingT := new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, test.matcher) + if !strings.Contains(stubTestingT.MockTestOutput, test.expected) { + innerT.Errorf("%s did not fail with expected desc <%s>, got: %s", test.description, test.expected, stubTestingT.MockTestOutput) + } + }) + } +} +func TestSizeMapMatcherDescription(t *testing.T) { + var equalsItems = []struct { + description string + actual map[string]bool + matcher *gocrest.Matcher[map[string]bool] + expected string + }{ + {description: "Empty", actual: map[string]bool{"foo": true}, matcher: is.Empty[string, bool, map[string]bool](), expected: "empty value"}, + {description: "HasKey", actual: map[string]bool{"hi": true}, matcher: has.Key[string, bool]("foo"), expected: "map has key 'foo'"}, + {description: "HasKeys", actual: map[string]bool{"hi": true, "bye": false}, matcher: has.AllKeys[string, bool]("hi", "foo"), expected: "map has keys '[hi foo]'"}, + } + for _, test := range equalsItems { + t.Run(test.description, func(innerT *testing.T) { + stubTestingT := new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, test.matcher) + if !strings.Contains(stubTestingT.MockTestOutput, test.expected) { + innerT.Errorf("%s did not fail with expected desc <%s>, got: %s", test.description, test.expected, stubTestingT.MockTestOutput) + } + }) + } +} +func TestArrayContainsMatcherDescription(t *testing.T) { + var equalsItems = []struct { + description string + actual []string + matcher *gocrest.Matcher[[]string] + expected string + }{ + {description: "ArrayContaining", actual: []string{"Foo", "Bar"}, matcher: is.ArrayContaining("Baz", "Bing"), expected: "something that contains and "}, + {description: "ValueContainArrayMatching", actual: []string{"Foo", "Bar"}, matcher: is.ArrayMatching(is.EqualTo("Baz"), is.EqualTo("Bing")), expected: "something that contains > and >"}, + } + for _, test := range equalsItems { + t.Run(test.description, func(innerT *testing.T) { + stubTestingT := new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, test.matcher) + if !strings.Contains(stubTestingT.MockTestOutput, test.expected) { + innerT.Errorf("%s did not fail with expected desc <%s>, got: %s", test.description, test.expected, stubTestingT.MockTestOutput) + } + }) + } +} +func TestStringMatchersDescription(t *testing.T) { + var equalsItems = []struct { + description string + actual string + matcher *gocrest.Matcher[string] + expected string + }{ {description: "MatchesPattern", actual: "blarney stone", matcher: is.MatchForPattern("~123.?.*"), expected: "a value that matches pattern ~123.?.*"}, {description: "MatchesPattern (invalid regex)", actual: "blarney stone", matcher: is.MatchForPattern("+++"), expected: "error parsing regexp: missing argument to repetition operator: `+`"}, {description: "Prefix", actual: "blarney stone", matcher: has.Prefix("123"), expected: "value with prefix 123"}, - {description: "AllOf", actual: "abc", matcher: is.AllOf(is.EqualTo("abc"), is.ValueContaining("e", "f")), expected: "something that contains and "}, - {description: "AnyOf", actual: "abc", matcher: is.AnyOf(is.EqualTo("efg"), is.ValueContaining("e")), expected: "any of (value equal to or something that contains )"}, - {description: "HasKey", actual: map[string]bool{"hi": true}, matcher: has.Key("foo"), expected: "map has key 'foo'"}, - {description: "HasKeys", actual: map[string]bool{"hi": true, "bye": false}, matcher: has.AllKeys("hi", "foo"), expected: "map has keys '[hi foo]'"}, - {description: "LengthOf Composed", actual: "a", matcher: has.Length(is.GreaterThan(2)), expected: "value with length value greater than <2>"}, + {description: "AllOf", actual: "abc", matcher: is.AllOf(is.EqualTo("abc"), is.StringContaining("e", "f")), expected: "something that contains [e f]"}, + {description: "AnyOf", actual: "abc", matcher: is.AnyOf(is.EqualTo("efg"), is.StringContaining("e")), expected: "any of (value equal to or something that contains [e])"}, + {description: "LengthOf Composed", actual: "a", matcher: has.LengthMatching[string, string](is.GreaterThan(2)), expected: "value with length value greater than <2>"}, {description: "EqualToIgnoringWhitespace", actual: "a b c", matcher: is.EqualToIgnoringWhitespace("b c d"), expected: "ignoring whitespace value equal to "}, } for _, test := range equalsItems { @@ -584,24 +826,26 @@ func TestMatcherDescription(t *testing.T) { }) } } + func TestAllOfDescribesOnlyMismatches(testing *testing.T) { stubTestingT := new(StubTestingT) then.AssertThat(stubTestingT, "abc", is.AllOf( is.EqualTo("abc"), - is.ValueContaining("e", "f"), - is.Empty(), + is.StringContaining("e", "f"), + is.Empty[string, string, string](), )) - if !strings.Contains(stubTestingT.MockTestOutput, "Expected: something that contains and and empty value\n") { + if !strings.Contains(stubTestingT.MockTestOutput, "Expected: something that contains [e f] and empty value\n") { testing.Errorf("incorrect description:%s", stubTestingT.MockTestOutput) } } + func TestHasFieldDescribesMismatch(testing *testing.T) { type T struct { F string B string } expected := "X" - then.AssertThat(stubTestingT, new(T), has.FieldNamed(expected)) + then.AssertThat(stubTestingT, new(T), has.FieldNamed[*T](expected)) if !strings.Contains(stubTestingT.MockTestOutput, "struct with field X") && !strings.Contains(stubTestingT.MockTestOutput, "T{F string B string}") { testing.Errorf("incorrect description:%s", stubTestingT.MockTestOutput) @@ -615,7 +859,7 @@ func TestHasFunctionDescribesMismatch(testing *testing.T) { } actual := new(MyType) expected := "X" - then.AssertThat(stubTestingT, actual, has.FunctionNamed(expected)) + then.AssertThat(stubTestingT, actual, has.FunctionNamed[*MyType](expected)) if !strings.Contains(stubTestingT.MockTestOutput, "interface with function X") && !strings.Contains(stubTestingT.MockTestOutput, "MyType{B()F()}") { testing.Errorf("incorrect description:%s", stubTestingT.MockTestOutput) @@ -671,7 +915,7 @@ func TestTypeName(t *testing.T) { for _, tt := range tests { t.Run(tt.expected, func(t *testing.T) { stubTestingT := new(StubTestingT) - then.AssertThat(stubTestingT, tt.actual, has.TypeName(tt.expected)) + then.AssertThat(stubTestingT, tt.actual, has.TypeName[any](tt.expected)) then.AssertThat(t, stubTestingT.HasFailed(), is.EqualTo(tt.shouldFail).Reason(stubTestingT.MockTestOutput)) }) @@ -681,44 +925,53 @@ func TestTypeName(t *testing.T) { func TestNilArrayInterface(t *testing.T) { actual := nilResponse() - then.AssertThat(t, actual, is.Nil()) + then.AssertThat(t, actual, is.Nil[[]any]()) } -func nilResponse() []interface{} { +func nilResponse() []any { return nil } -func TestEveryElement(t *testing.T) { +func TestEveryStringElement(t *testing.T) { tests := []struct { - actual interface{} - expected []*gocrest.Matcher + actual []string + expected []*gocrest.Matcher[string] shouldFail bool }{ { actual: []string{"test1", "test2"}, - expected: []*gocrest.Matcher{is.EqualTo("test1"), is.EqualTo("test2")}, + expected: []*gocrest.Matcher[string]{is.EqualTo("test1"), is.EqualTo("test2")}, shouldFail: false, }, + } + for _, test := range tests { + stubTestingT := new(StubTestingT) + then.AssertThat(stubTestingT, test.actual, has.EveryElement(test.expected...)) + + then.AssertThat(t, stubTestingT.HasFailed(), is.EqualTo(test.shouldFail).Reason(stubTestingT.MockTestOutput)) + } +} +func TestEveryIntElement(t *testing.T) { + tests := []struct { + actual []int + expected []*gocrest.Matcher[int] + shouldFail bool + }{ { actual: []int{1, 2}, - expected: []*gocrest.Matcher{is.EqualTo(1), is.EqualTo(2)}, + expected: []*gocrest.Matcher[int]{is.EqualTo(1), is.EqualTo(2)}, shouldFail: false, }, - { - actual: []string{"test1", "test2"}, - expected: []*gocrest.Matcher{is.EqualTo("test1"), is.EqualTo("nottest")}, - shouldFail: true, - }, { actual: []int{1, 2}, - expected: []*gocrest.Matcher{ + expected: []*gocrest.Matcher[int]{ is.EqualTo(1), }, shouldFail: true, }, { actual: []int{1}, - expected: []*gocrest.Matcher{ + expected: []*gocrest.Matcher[int]{ is.EqualTo(1), is.EqualTo(2), }, @@ -732,42 +985,18 @@ func TestEveryElement(t *testing.T) { then.AssertThat(t, stubTestingT.HasFailed(), is.EqualTo(test.shouldFail).Reason(stubTestingT.MockTestOutput)) } } -func TestEveryElementPanic(t *testing.T) { - tests := []struct { - actual string - expected []*gocrest.Matcher - }{ - { - actual: "not a slice", - expected: []*gocrest.Matcher{ - is.Empty(), - }, - }, - } - - defer func() { - recover := recover() - then.AssertThat(t, recover, is.Not(is.Nil())) - }() - - for _, test := range tests { - stubTestingT := new(StubTestingT) - - then.AssertThat(stubTestingT, test.actual, has.EveryElement(test.expected...)) - } -} func TestStructValues(t *testing.T) { tests := []struct { - actual interface{} - expected has.StructMatchers + actual any + expected has.StructMatchers[string] shouldFail bool }{ { actual: struct { Id string }{Id: "Id"}, - expected: has.StructMatchers{ + expected: has.StructMatchers[string]{ "Id": has.Prefix("Id"), }, shouldFail: false, @@ -777,7 +1006,7 @@ func TestStructValues(t *testing.T) { Id string Id2 string }{Id: "Id", Id2: "Id2"}, - expected: has.StructMatchers{ + expected: has.StructMatchers[string]{ "Id": has.Prefix("Id"), }, shouldFail: false, @@ -786,8 +1015,8 @@ func TestStructValues(t *testing.T) { actual: struct { Id string }{}, - expected: has.StructMatchers{ - "Id": is.Empty(), + expected: has.StructMatchers[string]{ + "Id": is.Empty[string, string, string](), }, shouldFail: false, }, @@ -795,7 +1024,7 @@ func TestStructValues(t *testing.T) { actual: struct { Id string }{}, - expected: has.StructMatchers{ + expected: has.StructMatchers[string]{ "Id": is.EqualTo("something"), }, shouldFail: true, @@ -805,7 +1034,7 @@ func TestStructValues(t *testing.T) { Id string Id2 string }{}, - expected: has.StructMatchers{ + expected: has.StructMatchers[string]{ "Id2": is.EqualTo("something"), }, shouldFail: true, @@ -815,7 +1044,7 @@ func TestStructValues(t *testing.T) { Id string Id2 string }{}, - expected: has.StructMatchers{ + expected: has.StructMatchers[string]{ "Id": is.EqualTo("Id"), "Id2": is.EqualTo("something"), }, @@ -824,7 +1053,7 @@ func TestStructValues(t *testing.T) { } for _, test := range tests { stubTestingT := new(StubTestingT) - then.AssertThat(stubTestingT, test.actual, has.StructWithValues(test.expected)) + then.AssertThat(stubTestingT, test.actual, has.StructWithValues[any](test.expected)) then.AssertThat(t, stubTestingT.HasFailed(), is.EqualTo(test.shouldFail).Reason(stubTestingT.MockTestOutput)) } @@ -832,53 +1061,18 @@ func TestStructValues(t *testing.T) { func TestStructValuesPanicsWithStringActual(t *testing.T) { actual := "not a struct" - expected := has.StructMatchers{ - "Id": is.Empty(), + expected := has.StructMatchers[string]{ + "Id": is.Empty[string, string, string](), } defer func() { recover := recover() - then.AssertThat(t, recover, is.Not(is.Nil())) + then.AssertThat(t, recover, is.Not(is.Nil[any]())) }() - then.AssertThat(stubTestingT, actual, has.StructWithValues(expected)) -} - -func TestStructValuesPanic(t *testing.T) { - tests := []struct { - actual interface{} - expected has.StructMatchers - }{ - { - actual: struct { - Id string - }{}, - expected: has.StructMatchers{ - "Id2": is.Empty(), - }, - }, - { - actual: struct { - id string - }{}, - expected: has.StructMatchers{ - "id": is.Empty(), - }, - }, - } - - defer func() { - recover := recover() - then.AssertThat(t, recover, is.Not(is.Nil())) - }() - - for _, test := range tests { - stubTestingT := new(StubTestingT) - - then.AssertThat(stubTestingT, test.actual, has.StructWithValues(test.expected)) - } + then.AssertThat(stubTestingT, actual, has.StructWithValues[string](expected)) } func TestConformsToStringer(t *testing.T) { - then.AssertThat(t, is.Nil().String(), is.EqualTo("value that is ")) + then.AssertThat(t, is.Nil[any]().String(), is.EqualTo("value that is ")) } type DelayedReader struct { @@ -900,6 +1094,7 @@ func TestEventuallyWithDelayedReader(t *testing.T) { then.AssertThat(eventually, by.Reading(slowReader, 1024), is.EqualTo([]byte("abcdefghijklmnopqrstuv"))) }) } + func TestEventuallyChannels(t *testing.T) { channel := firstTestChannel() then.Eventually(t, time.Second*5, time.Second, func(eventually gocrest.TestingT) { @@ -917,10 +1112,11 @@ func TestEventuallyChannelsShouldFail(t *testing.T) { }) then.AssertThat(t, stubbedTesting.failed, is.EqualTo(true)) then.AssertThat(t, stubbedTesting.MockTestOutput, is.AllOf( - is.ValueContaining("This is going to fail"), - is.Not(is.ValueContaining("should not fail")), + is.StringContaining("This is going to fail"), + is.Not(is.StringContaining("should not fail")), )) } + func TestEventuallyChannelInterface(t *testing.T) { type MyType struct { F string @@ -939,13 +1135,22 @@ func TestEventuallyChannelInterface(t *testing.T) { } }() then.WithinFiveSeconds(t, func(eventually gocrest.TestingT) { - then.AssertThat(eventually, by.Channelling(channel), has.StructWithValues(has.StructMatchers{ + then.AssertThat(eventually, by.Channelling(channel), has.StructWithValues[*MyType](has.StructMatchers[string]{ "F": is.EqualTo("hi - 3"), "B": is.EqualTo("bye - 3"), })) }) } +func TestCallingFunctionEventually(t *testing.T) { + function := func(a string) string { + time.Sleep(time.Second) + return a + } + then.WithinFiveSeconds(t, func(eventually gocrest.TestingT) { + then.AssertThat(eventually, by.Calling[string, string](function, "hi"), is.EqualTo("hi")) + }) +} func firstTestChannel() chan int { channel := make(chan int, 1) go func() { diff --git a/then/assertthat.go b/then/assertthat.go index f40ddd1..2b90499 100644 --- a/then/assertthat.go +++ b/then/assertthat.go @@ -6,7 +6,7 @@ import ( ) // AssertThat calls a given matcher and fails the test with a message if the matcher doesn't match. -func AssertThat(t gocrest.TestingT, actual interface{}, m *gocrest.Matcher) { +func AssertThat[A any](t gocrest.TestingT, actual A, m *gocrest.Matcher[A]) { t.Helper() matches := m.Matches(actual) if !matches { @@ -19,7 +19,7 @@ func AssertThat(t gocrest.TestingT, actual interface{}, m *gocrest.Matcher) { } } -func actualAsString(matcher *gocrest.Matcher, actual interface{}) string { +func actualAsString[A any](matcher *gocrest.Matcher[A], actual A) string { if matcher.Actual != "" { return matcher.Actual } diff --git a/then/eventually.go b/then/eventually.go index 7331b98..99c98bb 100644 --- a/then/eventually.go +++ b/then/eventually.go @@ -8,16 +8,20 @@ import ( "time" ) +// FailureLog is a type that holds temporary test data whilst Eventually runs type FailureLog struct { TestOutput string failed bool } + +// RecordingTestingT is a testingT interface that holds temporary test data whilst Eventually runs. Only used with Eventually type RecordingTestingT struct { sync.Mutex gocrest.TestingT failures []FailureLog } +// Errorf appends an error message into the failure buffer. Only used with Eventually func (t *RecordingTestingT) Errorf(format string, args ...interface{}) { t.Lock() defer t.Unlock() @@ -26,12 +30,21 @@ func (t *RecordingTestingT) Errorf(format string, args ...interface{}) { true, }) } + +// Fail calls Errorf with a generic failure message in case the test is failed from inside the value under test. +// Only used with Eventually func (t *RecordingTestingT) Fail() { t.Errorf("Unknown call to Fail") } + +// FailNow calls Errorf with a generic failure message in case the test is failed from inside the value under test. +// Only used with Eventually func (t *RecordingTestingT) FailNow() { t.Errorf("Unknown call to FailNow") } + +// FailedTestOutputs retrieves the list of recorded testing.T failures from the assertions passed in for Eventually evaluation. +// Only used with Eventually func (t *RecordingTestingT) FailedTestOutputs() []string { t.Lock() defer t.Unlock() @@ -41,6 +54,9 @@ func (t *RecordingTestingT) FailedTestOutputs() []string { } return logs } + +// Failing determines whether any of the Eventually assertions are still failing. +// Only used with Eventually func (t *RecordingTestingT) Failing() bool { t.Lock() defer t.Unlock() @@ -52,16 +68,23 @@ func (t *RecordingTestingT) Failing() bool { return false } +// Latest is used internally by Eventually to record the last recorded test output from assertions passed into it +// Only used with Eventually type Latest struct { sync.Mutex latestValue RecordingTestingT } +// Get is used internally by Eventually to Get the last recorded test output from assertions passed into it +// Only used with Eventually func (l *Latest) Get() RecordingTestingT { l.Lock() defer l.Unlock() return l.latestValue } + +// Merge is used internally by Eventually to Merge the latest recorded test output from assertions passed into it with the last one +// Only used with Eventually func (l *Latest) Merge(updated RecordingTestingT) RecordingTestingT { l.Lock() defer l.Unlock() @@ -84,7 +107,20 @@ func (l *Latest) Merge(updated RecordingTestingT) RecordingTestingT { } return merged } -func Eventually(t gocrest.TestingT, waitFor time.Duration, tick time.Duration, assertions func(eventually gocrest.TestingT)) { + +// Eventually retries a set of assertions passed into it from a function. +// waitFor is the amount of time to wait for the assertions to pass. +// tick is the amount of time between running the assertions +// assertions are the function containing the assertions. +// e.g: +// ``` +// +// then.Eventually(t, time.Second*5, time.Second, func(eventually gocrest.TestingT) { +// then.AssertThat(eventually, by.Channelling(channel), is.EqualTo(3).Reason("should not fail")) +// }) +// +// ``` +func Eventually(t gocrest.TestingT, waitFor, tick time.Duration, assertions func(eventually gocrest.TestingT)) { t.Helper() channel := make(chan RecordingTestingT, 1) @@ -101,7 +137,7 @@ func Eventually(t gocrest.TestingT, waitFor time.Duration, tick time.Duration, a select { case <-timer.C: latestRecordingT := latestValue.Get() - t.Errorf("Eventually Failed: \n" + strings.Join(latestRecordingT.FailedTestOutputs(), "\n")) + t.Errorf(fmt.Sprintf("Eventually Failed after %s: \n", waitFor) + strings.Join(latestRecordingT.FailedTestOutputs(), "\n")) return case <-tick: tick = nil @@ -123,9 +159,13 @@ func Eventually(t gocrest.TestingT, waitFor time.Duration, tick time.Duration, a } } + +// WithinTenSeconds is a shortcut for a ten-second Eventually call with one second tick func WithinTenSeconds(t gocrest.TestingT, assertions func(eventually gocrest.TestingT)) { Eventually(t, time.Duration(10)*time.Second, time.Duration(1)*time.Second, assertions) } + +// WithinFiveSeconds is a shortcut for a five-second Eventually call with one second tick func WithinFiveSeconds(t gocrest.TestingT, assertions func(eventually gocrest.TestingT)) { Eventually(t, time.Duration(10)*time.Second, time.Duration(1)*time.Second, assertions) }