From 8a747c0638fce6c09f4de06948af31622f9b80f7 Mon Sep 17 00:00:00 2001 From: Papapetrou Patroklos <1743100+ppapapetrou76@users.noreply.github.com> Date: Fri, 23 Apr 2021 13:35:23 +0300 Subject: [PATCH] adds basic error assertions (#23) --- .golangci.yml | 1 + assert/error.go | 225 +++++++---------------------- assert/error_messages.go | 190 ++++++++++++++++++++++++ assert/error_messages_test.go | 90 ++++++++++++ assert/error_test.go | 177 +++++++++++++++-------- internal/pkg/values/error_value.go | 29 ++++ 6 files changed, 479 insertions(+), 233 deletions(-) create mode 100644 assert/error_messages.go create mode 100644 assert/error_messages_test.go create mode 100644 internal/pkg/values/error_value.go diff --git a/.golangci.yml b/.golangci.yml index b687623..7a8e068 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -16,6 +16,7 @@ issues: - golint - paralleltest - testpackage + - goerr113 - path: github/client linters: - gosec diff --git a/assert/error.go b/assert/error.go index 285ec71..780ad9f 100644 --- a/assert/error.go +++ b/assert/error.go @@ -1,190 +1,75 @@ package assert import ( - "fmt" - "reflect" - "strings" + "testing" - utils2 "github.com/ppapapetrou76/go-testing/internal/pkg/utils" - "github.com/ppapapetrou76/go-testing/types" - "github.com/r3labs/diff/v2" + "github.com/ppapapetrou76/go-testing/internal/pkg/values" ) -func shouldBeEqual(actual types.Assertable, expected interface{}) string { - diffMessage := strings.Builder{} - skipDetailedDiff := utils2.HasUnexportedFields(reflect.ValueOf(expected)) || utils2.HasUnexportedFields(reflect.ValueOf(actual.Value())) - - if !skipDetailedDiff { - diffs, _ := diff.Diff(expected, actual.Value()) - for _, d := range diffs { - if len(d.Path) == 0 { - continue - } - switch d.Type { - case "delete": - path := strings.Join(d.Path, ":") - diffMessage.WriteString(fmt.Sprintf("actual value of %+v is expected but missing from %s\n", d.To, path)) - case "create": - path := strings.Join(d.Path, ":") - diffMessage.WriteString(fmt.Sprintf("actual value of %+v is not expected in %s\n", d.To, path)) - case "update": - path := strings.Join(d.Path, ":") - diffMessage.WriteString(fmt.Sprintf("actual value of %+v is different in %s from %+v\n", d.To, path, d.From)) - } - } - } - - return fmt.Sprintf("assertion failed:\nexpected value\t:%+v\nactual value\t:%+v\n%s", expected, actual.Value(), diffMessage.String()) -} - -func shouldNotBeEqual(actual types.Assertable, expected interface{}) string { - return fmt.Sprintf("assertion failed: expected value of = %+v, to be other than %+v", actual.Value(), expected) -} - -func shouldBeGreater(actual types.Assertable, expected interface{}) string { - return fmt.Sprintf("assertion failed: expected value of = %+v, to be greater than %+v", actual.Value(), expected) -} - -func shouldBeGreaterOrEqual(actual types.Assertable, expected interface{}) string { - return fmt.Sprintf("assertion failed: expected value of = %+v, to be greater than or equal to %+v", actual.Value(), expected) -} - -func shouldBeLessThan(actual types.Assertable, expected interface{}) string { - return fmt.Sprintf("assertion failed: expected value of = %+v, to be less than %+v", actual.Value(), expected) -} - -func shouldBeLessOrEqual(actual types.Assertable, expected interface{}) string { - return fmt.Sprintf("assertion failed: expected value of = %+v, to be less than or equal to %+v", actual.Value(), expected) -} - -func shouldBeEmpty(actual types.Assertable) string { - return fmt.Sprintf("assertion failed: expected %+v to be empty, but it's not", actual.Value()) -} - -func shouldNotBeEmpty(actual types.Assertable) string { - return fmt.Sprintf("assertion failed: expected %+v not to be empty, but it is", actual.Value()) -} - -func shouldBeNil(actual types.Assertable) string { - return fmt.Sprintf("assertion failed: expected value of = %+v, to be nil but it wasn't", actual.Value()) -} - -func shouldNotBeNil(actual types.Assertable) string { - return fmt.Sprintf("assertion failed: expected value of = %+v, to be non-nil but it was", actual.Value()) -} - -func shouldHaveSize(actual types.Sizeable, expected int) string { - return fmt.Sprintf("assertion failed: expected size of = [%d], to be but it has size of [%d] ", actual.Size(), expected) -} - -func shouldContain(actual types.Assertable, elements interface{}) string { - return fmt.Sprintf("assertion failed: containable [%v] should contain [%+v], but it doesn't", actual.Value(), elements) -} - -func shouldContainIgnoringCase(actual types.Assertable, elements interface{}) string { - return fmt.Sprintf("assertion failed: containable [%v] should contain [%+v] ignoring case, but it doesn't", actual.Value(), elements) -} - -func shouldContainOnly(actual types.Assertable, elements interface{}) string { - return fmt.Sprintf("assertion failed: containable [%v] should contain only [%+v], but it doesn't", actual.Value(), elements) -} - -func shouldContainOnlyOnce(actual types.Assertable, elements interface{}) string { - return fmt.Sprintf("assertion failed: containable [%v] should contain [%+v] only once, but it doesn't", actual.Value(), elements) -} - -func shouldContainWhiteSpace(actual types.Assertable) string { - return fmt.Sprintf("assertion failed: containable [%v] should contain whitespace(s), but it doesn't", actual.Value()) -} - -func shouldNotContainAnyWhiteSpace(actual types.Assertable) string { - return fmt.Sprintf("assertion failed: containable [%v] should not contain any whitespace, but it does", actual.Value()) -} - -func shouldNotContain(actual types.Assertable, elements interface{}) string { - return fmt.Sprintf("assertion failed: containable [%v] should not contain [%+v], but it does", actual.Value(), elements) +// AssertableError is the assertable structure for error values. +type AssertableError struct { + t *testing.T + actual values.ErrorValue } -func shouldBeMap(actual types.Assertable) string { - return fmt.Sprintf("assertion failed: assertable should be a map but it is %T", reflect.ValueOf(actual.Value()).Kind()) -} - -func shouldHaveKey(actual types.Assertable, elements interface{}) string { - return fmt.Sprintf("assertion failed: map [%v] should have the key [%+v], but it doesn't", actual.Value(), elements) -} - -func shouldHaveValue(actual types.Assertable, elements interface{}) string { - return fmt.Sprintf("assertion failed: map [%v] should have the value [%+v], but it doesn't", actual.Value(), elements) -} - -func shouldHaveEntry(actual types.Assertable, entry types.MapEntry) string { - return fmt.Sprintf("assertion failed: map [%v] should have the entry [%+v], but it doesn't", actual.Value(), entry) -} - -func shouldNotHaveKey(actual types.Assertable, elements interface{}) string { - return fmt.Sprintf("assertion failed: map [%v] should not have the key [%+v], but it does", actual.Value(), elements) -} - -func shouldNotHaveValue(actual types.Assertable, elements interface{}) string { - return fmt.Sprintf("assertion failed: map [%v] should not have the value [%+v], but it does", actual.Value(), elements) -} - -func shouldNotHaveEntry(actual types.Assertable, entry types.MapEntry) string { - return fmt.Sprintf("assertion failed: map [%v] should not have the entry [%+v], but it does", actual.Value(), entry) -} - -func shouldStartWith(actual types.Assertable, substr string) string { - return fmt.Sprintf("assertion failed: expected value of [%v] to start with [%+v], but it doesn't", actual.Value(), substr) -} - -func shouldNotStartWith(actual types.Assertable, substr string) string { - return fmt.Sprintf("assertion failed: expected value of [%v] to not start with [%+v], but it does", actual.Value(), substr) -} - -func shouldEndWith(actual types.Assertable, substr string) string { - return fmt.Sprintf("assertion failed: expected value of [%v] to end with [%+v], but it doesn't", actual.Value(), substr) -} - -func shouldNotEndWith(actual types.Assertable, substr string) string { - return fmt.Sprintf("assertion failed: expected value of [%v] to not end with [%+v], but it does", actual.Value(), substr) -} - -func shouldHaveSameSizeAs(actual types.Assertable, substr string) string { - return fmt.Sprintf("assertion failed: expected size of [%v] should be same as the size of [%+v], but it isn't", actual.Value(), substr) -} - -func shouldHaveType(actual types.Assertable, value interface{}) string { - return fmt.Sprintf("assertion failed: expected value of = %+v, to have type of %T but it hasn't", actual.Value(), value) -} - -func shouldBeShorter(actual types.Assertable, expected interface{}) string { - return fmt.Sprintf("assertion failed: expected value of = %+v, to be greater than %+v", actual.Value(), expected) +// ThatError returns an AssertableError structure initialized with the test reference and the actual value to assert. +func ThatError(t *testing.T, actual error) AssertableError { + t.Helper() + return AssertableError{ + t: t, + actual: values.NewErrorValue(actual), + } } -func shouldBeLonger(actual types.Assertable, expected interface{}) string { - return fmt.Sprintf("assertion failed: expected value of = %+v, to be longer than %+v", actual.Value(), expected) +// IsNil asserts if the expected error is nil. +func (a AssertableError) IsNil() AssertableError { + errAnyValue := values.NewAnyValue(a.actual.Value()) + if errAnyValue.IsNotNil() { + a.t.Error(shouldBeNil(errAnyValue)) + } + return a } -func shouldContainOnlyDigits(actual types.Assertable) string { - return fmt.Sprintf("assertion failed: expected %+v to have only digits, but it's not", actual.Value()) +// IsNotNil asserts if the expected error is nil. +func (a AssertableError) IsNotNil() AssertableError { + errAnyValue := values.NewAnyValue(a.actual.Value()) + if errAnyValue.IsNil() { + a.t.Error(shouldNotBeNil(errAnyValue)) + } + return a } -func shouldBeLowerCase(actual types.Assertable) string { - return fmt.Sprintf("assertion failed: expected %+v to be lower case, but it's not", actual.Value()) -} +// HasExactMessage asserts if the expected error contains exactly the given message. +func (a AssertableError) HasExactMessage(expectedMessage string) AssertableError { + errAnyValue := values.NewAnyValue(a.actual.Value()) + if errAnyValue.IsNil() { + a.t.Error(shouldContain(errAnyValue, expectedMessage)) + return a + } -func shouldBeUpperCase(actual types.Assertable) string { - return fmt.Sprintf("assertion failed: expected %+v to be upper case, but it's not", actual.Value()) + errStringValue := values.NewStringValue(a.actual.Error().Error()) + if !errStringValue.ContainsOnly(expectedMessage) { + a.t.Error(shouldContain(errAnyValue, expectedMessage)) + } + return a } -func shouldBeAlmostSame(actual types.Assertable, expected interface{}) string { - return fmt.Sprintf("assertion failed: expected value of = %+v, to be almost the same as %+v", actual.Value(), expected) -} +// IsSameAs asserts if the expected error is the same with the given error. +func (a AssertableError) IsSameAs(err error) AssertableError { + actualAnyValue := values.NewAnyValue(a.actual.Value()) + expectedAnyValue := values.NewAnyValue(err) -func shouldBeDefined(actual types.Assertable) string { - return fmt.Sprintf("assertion failed: expected value of = %+v, to be defined but it was not", actual.Value()) -} + if actualAnyValue.IsNil() != expectedAnyValue.IsNil() { + a.t.Error(shouldBeEqual(a.actual, expectedAnyValue)) + return a + } + if actualAnyValue.IsNil() && expectedAnyValue.IsNil() { + return a + } -func shouldNotBeDefined(actual types.Assertable) string { - return fmt.Sprintf("assertion failed: expected value of = %+v, to be un-defined but it was", actual.Value()) + actualStringValue := values.NewStringValue(a.actual.Error().Error()) + if !actualStringValue.IsEqualTo(err.Error()) { + a.t.Error(shouldBeEqual(a.actual, err.Error())) + } + return a } diff --git a/assert/error_messages.go b/assert/error_messages.go new file mode 100644 index 0000000..285ec71 --- /dev/null +++ b/assert/error_messages.go @@ -0,0 +1,190 @@ +package assert + +import ( + "fmt" + "reflect" + "strings" + + utils2 "github.com/ppapapetrou76/go-testing/internal/pkg/utils" + "github.com/ppapapetrou76/go-testing/types" + "github.com/r3labs/diff/v2" +) + +func shouldBeEqual(actual types.Assertable, expected interface{}) string { + diffMessage := strings.Builder{} + skipDetailedDiff := utils2.HasUnexportedFields(reflect.ValueOf(expected)) || utils2.HasUnexportedFields(reflect.ValueOf(actual.Value())) + + if !skipDetailedDiff { + diffs, _ := diff.Diff(expected, actual.Value()) + for _, d := range diffs { + if len(d.Path) == 0 { + continue + } + switch d.Type { + case "delete": + path := strings.Join(d.Path, ":") + diffMessage.WriteString(fmt.Sprintf("actual value of %+v is expected but missing from %s\n", d.To, path)) + case "create": + path := strings.Join(d.Path, ":") + diffMessage.WriteString(fmt.Sprintf("actual value of %+v is not expected in %s\n", d.To, path)) + case "update": + path := strings.Join(d.Path, ":") + diffMessage.WriteString(fmt.Sprintf("actual value of %+v is different in %s from %+v\n", d.To, path, d.From)) + } + } + } + + return fmt.Sprintf("assertion failed:\nexpected value\t:%+v\nactual value\t:%+v\n%s", expected, actual.Value(), diffMessage.String()) +} + +func shouldNotBeEqual(actual types.Assertable, expected interface{}) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to be other than %+v", actual.Value(), expected) +} + +func shouldBeGreater(actual types.Assertable, expected interface{}) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to be greater than %+v", actual.Value(), expected) +} + +func shouldBeGreaterOrEqual(actual types.Assertable, expected interface{}) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to be greater than or equal to %+v", actual.Value(), expected) +} + +func shouldBeLessThan(actual types.Assertable, expected interface{}) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to be less than %+v", actual.Value(), expected) +} + +func shouldBeLessOrEqual(actual types.Assertable, expected interface{}) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to be less than or equal to %+v", actual.Value(), expected) +} + +func shouldBeEmpty(actual types.Assertable) string { + return fmt.Sprintf("assertion failed: expected %+v to be empty, but it's not", actual.Value()) +} + +func shouldNotBeEmpty(actual types.Assertable) string { + return fmt.Sprintf("assertion failed: expected %+v not to be empty, but it is", actual.Value()) +} + +func shouldBeNil(actual types.Assertable) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to be nil but it wasn't", actual.Value()) +} + +func shouldNotBeNil(actual types.Assertable) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to be non-nil but it was", actual.Value()) +} + +func shouldHaveSize(actual types.Sizeable, expected int) string { + return fmt.Sprintf("assertion failed: expected size of = [%d], to be but it has size of [%d] ", actual.Size(), expected) +} + +func shouldContain(actual types.Assertable, elements interface{}) string { + return fmt.Sprintf("assertion failed: containable [%v] should contain [%+v], but it doesn't", actual.Value(), elements) +} + +func shouldContainIgnoringCase(actual types.Assertable, elements interface{}) string { + return fmt.Sprintf("assertion failed: containable [%v] should contain [%+v] ignoring case, but it doesn't", actual.Value(), elements) +} + +func shouldContainOnly(actual types.Assertable, elements interface{}) string { + return fmt.Sprintf("assertion failed: containable [%v] should contain only [%+v], but it doesn't", actual.Value(), elements) +} + +func shouldContainOnlyOnce(actual types.Assertable, elements interface{}) string { + return fmt.Sprintf("assertion failed: containable [%v] should contain [%+v] only once, but it doesn't", actual.Value(), elements) +} + +func shouldContainWhiteSpace(actual types.Assertable) string { + return fmt.Sprintf("assertion failed: containable [%v] should contain whitespace(s), but it doesn't", actual.Value()) +} + +func shouldNotContainAnyWhiteSpace(actual types.Assertable) string { + return fmt.Sprintf("assertion failed: containable [%v] should not contain any whitespace, but it does", actual.Value()) +} + +func shouldNotContain(actual types.Assertable, elements interface{}) string { + return fmt.Sprintf("assertion failed: containable [%v] should not contain [%+v], but it does", actual.Value(), elements) +} + +func shouldBeMap(actual types.Assertable) string { + return fmt.Sprintf("assertion failed: assertable should be a map but it is %T", reflect.ValueOf(actual.Value()).Kind()) +} + +func shouldHaveKey(actual types.Assertable, elements interface{}) string { + return fmt.Sprintf("assertion failed: map [%v] should have the key [%+v], but it doesn't", actual.Value(), elements) +} + +func shouldHaveValue(actual types.Assertable, elements interface{}) string { + return fmt.Sprintf("assertion failed: map [%v] should have the value [%+v], but it doesn't", actual.Value(), elements) +} + +func shouldHaveEntry(actual types.Assertable, entry types.MapEntry) string { + return fmt.Sprintf("assertion failed: map [%v] should have the entry [%+v], but it doesn't", actual.Value(), entry) +} + +func shouldNotHaveKey(actual types.Assertable, elements interface{}) string { + return fmt.Sprintf("assertion failed: map [%v] should not have the key [%+v], but it does", actual.Value(), elements) +} + +func shouldNotHaveValue(actual types.Assertable, elements interface{}) string { + return fmt.Sprintf("assertion failed: map [%v] should not have the value [%+v], but it does", actual.Value(), elements) +} + +func shouldNotHaveEntry(actual types.Assertable, entry types.MapEntry) string { + return fmt.Sprintf("assertion failed: map [%v] should not have the entry [%+v], but it does", actual.Value(), entry) +} + +func shouldStartWith(actual types.Assertable, substr string) string { + return fmt.Sprintf("assertion failed: expected value of [%v] to start with [%+v], but it doesn't", actual.Value(), substr) +} + +func shouldNotStartWith(actual types.Assertable, substr string) string { + return fmt.Sprintf("assertion failed: expected value of [%v] to not start with [%+v], but it does", actual.Value(), substr) +} + +func shouldEndWith(actual types.Assertable, substr string) string { + return fmt.Sprintf("assertion failed: expected value of [%v] to end with [%+v], but it doesn't", actual.Value(), substr) +} + +func shouldNotEndWith(actual types.Assertable, substr string) string { + return fmt.Sprintf("assertion failed: expected value of [%v] to not end with [%+v], but it does", actual.Value(), substr) +} + +func shouldHaveSameSizeAs(actual types.Assertable, substr string) string { + return fmt.Sprintf("assertion failed: expected size of [%v] should be same as the size of [%+v], but it isn't", actual.Value(), substr) +} + +func shouldHaveType(actual types.Assertable, value interface{}) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to have type of %T but it hasn't", actual.Value(), value) +} + +func shouldBeShorter(actual types.Assertable, expected interface{}) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to be greater than %+v", actual.Value(), expected) +} + +func shouldBeLonger(actual types.Assertable, expected interface{}) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to be longer than %+v", actual.Value(), expected) +} + +func shouldContainOnlyDigits(actual types.Assertable) string { + return fmt.Sprintf("assertion failed: expected %+v to have only digits, but it's not", actual.Value()) +} + +func shouldBeLowerCase(actual types.Assertable) string { + return fmt.Sprintf("assertion failed: expected %+v to be lower case, but it's not", actual.Value()) +} + +func shouldBeUpperCase(actual types.Assertable) string { + return fmt.Sprintf("assertion failed: expected %+v to be upper case, but it's not", actual.Value()) +} + +func shouldBeAlmostSame(actual types.Assertable, expected interface{}) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to be almost the same as %+v", actual.Value(), expected) +} + +func shouldBeDefined(actual types.Assertable) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to be defined but it was not", actual.Value()) +} + +func shouldNotBeDefined(actual types.Assertable) string { + return fmt.Sprintf("assertion failed: expected value of = %+v, to be un-defined but it was", actual.Value()) +} diff --git a/assert/error_messages_test.go b/assert/error_messages_test.go new file mode 100644 index 0000000..4a5296d --- /dev/null +++ b/assert/error_messages_test.go @@ -0,0 +1,90 @@ +package assert + +import ( + "testing" + + "github.com/ppapapetrou76/go-testing/internal/pkg/values" + "github.com/ppapapetrou76/go-testing/types" +) + +func Test_shouldBeEqual(t *testing.T) { + type assertedStruct struct { + BoolField bool + StringField string + IntField int + SliceField []string + } + type assertedStructUnexportedFields struct { + boolField bool + } + + tests := []struct { + name string + actual types.Assertable + expected interface{} + expectedMessage string + }{ + { + name: "should return expected message when structs have unexported fields", + actual: values.NewStructValue(assertedStructUnexportedFields{}), + expected: assertedStructUnexportedFields{ + boolField: true, + }, + expectedMessage: "assertion failed:\n" + + "expected value\t:{boolField:true}\n" + + "actual value\t:{boolField:false}\n", + }, + { + name: "should return expected message when slices are not equal", + actual: values.NewSliceValue([]string{"elem1", "elem2", "elem3"}), + expected: []string{"elem1", "elem4"}, + expectedMessage: "assertion failed:\n" + + "expected value\t:[elem1 elem4]\n" + + "actual value\t:[elem1 elem2 elem3]\n" + + "actual value of elem2 is different in 1 from elem4\n" + + "actual value of elem3 is not expected in 2\n", + }, + { + name: "should return expected message when maps are not equal", + actual: values.NewKeyStringMap(map[string]int{"1": 2, "2": 2}), + expected: map[string]int{"1": 1}, + expectedMessage: "assertion failed:\n" + + "expected value\t:map[1:1]\n" + + "actual value\t:map[1:2 2:2]\n" + + "actual value of 2 is different in 1 from 1\n" + + "actual value of 2 is not expected in 2\n", + }, + { + name: "should return expected message when simple types are not equal", + actual: values.NewStringValue("i'm a simple type"), + expected: "i'm a simple type but not equal", + expectedMessage: "assertion failed:\nexpected value\t:i'm a simple type but not equal\nactual value\t:i'm a simple type\n", + }, + { + name: "should return expected message when structs are not equal", + actual: values.NewStructValue(assertedStruct{ + SliceField: []string{"elem3"}, + }), + expected: assertedStruct{ + BoolField: true, + StringField: "some-value", + IntField: 100, + SliceField: []string{"elem1", "elem2"}, + }, + expectedMessage: "assertion failed:\nexpected value\t:" + + "{BoolField:true StringField:some-value IntField:100 SliceField:[elem1 elem2]}\n" + + "actual value\t:{BoolField:false StringField: IntField:0 SliceField:[elem3]}\n" + + "actual value of false is different in BoolField from true\n" + + "actual value of is different in StringField from some-value\n" + + "actual value of 0 is different in IntField from 100\n" + + "actual value of elem3 is different in SliceField:0 from elem1\n" + + "actual value of is expected but missing from SliceField:1\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualMessage := shouldBeEqual(tt.actual, tt.expected) + That(t, actualMessage).IsEqualTo(tt.expectedMessage) + }) + } +} diff --git a/assert/error_test.go b/assert/error_test.go index 4a5296d..8ed8da0 100644 --- a/assert/error_test.go +++ b/assert/error_test.go @@ -1,90 +1,141 @@ package assert import ( + "errors" "testing" - - "github.com/ppapapetrou76/go-testing/internal/pkg/values" - "github.com/ppapapetrou76/go-testing/types" ) -func Test_shouldBeEqual(t *testing.T) { - type assertedStruct struct { - BoolField bool - StringField string - IntField int - SliceField []string +func TestAssertableError_IsNil(t *testing.T) { + tests := []struct { + name string + actual error + shouldFail bool + }{ + { + name: "should assert nil error", + actual: nil, + shouldFail: false, + }, + { + name: "should assert not nil error", + actual: errors.New("some error"), + shouldFail: true, + }, } - type assertedStructUnexportedFields struct { - boolField bool + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + test := &testing.T{} + ThatError(test, tt.actual).IsNil() + ThatBool(t, test.Failed()).IsEqualTo(tt.shouldFail) + }) } +} + +func TestAssertableError_IsNotNil(t *testing.T) { + tests := []struct { + name string + actual error + shouldFail bool + }{ + { + name: "should assert nil error", + actual: nil, + shouldFail: true, + }, + { + name: "should assert not nil error", + actual: errors.New("some error"), + shouldFail: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + test := &testing.T{} + ThatError(test, tt.actual).IsNotNil() + ThatBool(t, test.Failed()).IsEqualTo(tt.shouldFail) + }) + } +} + +func TestAssertableError_HasExactMessage(t *testing.T) { + tests := []struct { + name string + actual error + message string + shouldFail bool + }{ + { + name: "should assert nil error", + actual: nil, + shouldFail: true, + }, + { + name: "should assert not nil error with exact message", + actual: errors.New("some error"), + message: "some error", + shouldFail: false, + }, + { + name: "should assert not nil error with different message", + actual: errors.New("completely different message"), + message: "some error", + shouldFail: true, + }, + { + name: "should assert not nil error that contains the message but not only that", + actual: errors.New("some error with additional information"), + message: "some error", + shouldFail: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + test := &testing.T{} + ThatError(test, tt.actual).HasExactMessage(tt.message) + ThatBool(t, test.Failed()).IsEqualTo(tt.shouldFail) + }) + } +} +func TestAssertableError_IsSameAs(t *testing.T) { tests := []struct { - name string - actual types.Assertable - expected interface{} - expectedMessage string + name string + actual error + expected error + shouldFail bool }{ { - name: "should return expected message when structs have unexported fields", - actual: values.NewStructValue(assertedStructUnexportedFields{}), - expected: assertedStructUnexportedFields{ - boolField: true, - }, - expectedMessage: "assertion failed:\n" + - "expected value\t:{boolField:true}\n" + - "actual value\t:{boolField:false}\n", + name: "should assert both nil errors", + shouldFail: false, }, { - name: "should return expected message when slices are not equal", - actual: values.NewSliceValue([]string{"elem1", "elem2", "elem3"}), - expected: []string{"elem1", "elem4"}, - expectedMessage: "assertion failed:\n" + - "expected value\t:[elem1 elem4]\n" + - "actual value\t:[elem1 elem2 elem3]\n" + - "actual value of elem2 is different in 1 from elem4\n" + - "actual value of elem3 is not expected in 2\n", + name: "should assert when actual error is nil and expected is not", + expected: errors.New("some error"), + shouldFail: true, }, { - name: "should return expected message when maps are not equal", - actual: values.NewKeyStringMap(map[string]int{"1": 2, "2": 2}), - expected: map[string]int{"1": 1}, - expectedMessage: "assertion failed:\n" + - "expected value\t:map[1:1]\n" + - "actual value\t:map[1:2 2:2]\n" + - "actual value of 2 is different in 1 from 1\n" + - "actual value of 2 is not expected in 2\n", + name: "should assert when actual error is not nil and expected is nil", + actual: errors.New("some error"), + shouldFail: true, }, { - name: "should return expected message when simple types are not equal", - actual: values.NewStringValue("i'm a simple type"), - expected: "i'm a simple type but not equal", - expectedMessage: "assertion failed:\nexpected value\t:i'm a simple type but not equal\nactual value\t:i'm a simple type\n", + name: "should assert when both errors are not nil and have the same message", + actual: errors.New("some error"), + expected: errors.New("some error"), + shouldFail: false, }, { - name: "should return expected message when structs are not equal", - actual: values.NewStructValue(assertedStruct{ - SliceField: []string{"elem3"}, - }), - expected: assertedStruct{ - BoolField: true, - StringField: "some-value", - IntField: 100, - SliceField: []string{"elem1", "elem2"}, - }, - expectedMessage: "assertion failed:\nexpected value\t:" + - "{BoolField:true StringField:some-value IntField:100 SliceField:[elem1 elem2]}\n" + - "actual value\t:{BoolField:false StringField: IntField:0 SliceField:[elem3]}\n" + - "actual value of false is different in BoolField from true\n" + - "actual value of is different in StringField from some-value\n" + - "actual value of 0 is different in IntField from 100\n" + - "actual value of elem3 is different in SliceField:0 from elem1\n" + - "actual value of is expected but missing from SliceField:1\n", + name: "should assert when both errors are not nil and have different message", + actual: errors.New("some error"), + expected: errors.New("some other error"), + shouldFail: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - actualMessage := shouldBeEqual(tt.actual, tt.expected) - That(t, actualMessage).IsEqualTo(tt.expectedMessage) + test := &testing.T{} + ThatError(test, tt.actual).IsSameAs(tt.expected) + ThatBool(t, test.Failed()).IsEqualTo(tt.shouldFail) }) } } diff --git a/internal/pkg/values/error_value.go b/internal/pkg/values/error_value.go new file mode 100644 index 0000000..bb56a32 --- /dev/null +++ b/internal/pkg/values/error_value.go @@ -0,0 +1,29 @@ +package values + +import "fmt" + +// ErrorValue is a struct that holds an error value. +type ErrorValue struct { + value error +} + +func (v ErrorValue) Error() error { + return v.value +} + +// Value returns the error value as an interface object. +func (v ErrorValue) Value() interface{} { + return v.value +} + +// NewErrorValue creates and returns a ErrorValue struct initialed with the given value. +func NewErrorValue(value interface{}) ErrorValue { + switch v := value.(type) { + case nil: + return ErrorValue{} + case error: + return ErrorValue{value: v} + default: + panic(fmt.Sprintf("expected error value type but got %T type", v)) + } +}