diff --git a/assert/bool.go b/assert/bool.go index a1bfdd3..5b8cfa3 100644 --- a/assert/bool.go +++ b/assert/bool.go @@ -24,6 +24,7 @@ func ThatBool(t *testing.T, actual bool) AssertableBool { // IsEqualTo asserts if the expected bool is equal to the assertable bool value // It errors the tests if the compared values (actual VS expected) are not equal. func (a AssertableBool) IsEqualTo(expected interface{}) AssertableBool { + a.t.Helper() if !a.actual.IsEqualTo(expected) { a.t.Error(shouldBeEqual(a.actual, expected)) } diff --git a/assert/error_messages.go b/assert/error_messages.go index be15cab..454d3d0 100644 --- a/assert/error_messages.go +++ b/assert/error_messages.go @@ -200,3 +200,7 @@ func shouldBeDefined(actual types.Assertable) string { func shouldNotBeDefined(actual types.Assertable) string { return fmt.Sprintf("assertion failed: expected value of = %+v, to be un-defined but it was", actual.Value()) } + +func shouldContainUniqueElements(actual types.Assertable) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to contain unique elements but it doesn't", actual.Value()) +} diff --git a/assert/slice.go b/assert/slice.go index da97325..3e2a38a 100644 --- a/assert/slice.go +++ b/assert/slice.go @@ -106,3 +106,12 @@ func (a AssertableSlice) DoesNotContain(elements interface{}) AssertableSlice { } return a } + +// HasUniqueElements asserts if the assertable string slice does not contain the given element +// It errors the test if it contains it/them. +func (a AssertableSlice) HasUniqueElements() AssertableSlice { + if !(a.actual.HasUniqueElements()) { + a.t.Error(shouldContainUniqueElements(a.actual)) + } + return a +} diff --git a/assert/slice_test.go b/assert/slice_test.go index 3374594..e6df28c 100644 --- a/assert/slice_test.go +++ b/assert/slice_test.go @@ -329,3 +329,34 @@ func TestAssertableSlice_DoesNotContain(t *testing.T) { }) } } + +func TestAssertableSlice_HasUniqueElements(t *testing.T) { + tests := []struct { + name string + actual []string + shouldFail bool + }{ + { + name: "should not fail if slice has unique elements", + actual: []string{"element", "element2", "element3"}, + shouldFail: false, + }, + { + name: "should fail if slice has unique elements", + actual: []string{"element", "element", "element3"}, + shouldFail: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + test := &testing.T{} + ThatSlice(test, tt.actual).HasUniqueElements() + ThatBool(t, test.Failed()).IsEqualTo(tt.shouldFail) + }) + } + test := &testing.T{} + ThatSlice(test, []int{1, 2, 3}).HasUniqueElements() + ThatBool(t, test.Failed()).IsEqualTo(false) + ThatSlice(test, []int{1, 1, 3}).HasUniqueElements() + ThatBool(t, test.Failed()).IsEqualTo(true) +} diff --git a/internal/pkg/values/slice_value.go b/internal/pkg/values/slice_value.go index 0c5403f..bef316e 100644 --- a/internal/pkg/values/slice_value.go +++ b/internal/pkg/values/slice_value.go @@ -83,6 +83,23 @@ func (s SliceValue) ContainsOnly(elements interface{}) bool { return s.Contains(elements) && s.HasSize(reflect.ValueOf(elements).Len()) } +// HasUniqueElements returns true if the slice contains only unique elements else false. +func (s SliceValue) HasUniqueElements() bool { + if !IsSlice(s.Value()) { + return false + } + sliceValue := reflect.ValueOf(s.value) + elements := map[interface{}]bool{} + + for i := 0; i < sliceValue.Len(); i++ { + if _, ok := elements[sliceValue.Index(i).Interface()]; ok { + return false + } + elements[sliceValue.Index(i).Interface()] = true + } + return true +} + // Value returns the actual value of the structure. func (s SliceValue) Value() interface{} { return s.value diff --git a/types/types.go b/types/types.go index ef3425d..c58e5f4 100644 --- a/types/types.go +++ b/types/types.go @@ -34,6 +34,7 @@ type Containable interface { Contains(elements interface{}) bool ContainsOnly(elements interface{}) bool DoesNotContain(elements interface{}) bool + HasUniqueElements() bool Sizeable }