-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor to improve error descriptions
- Loading branch information
Showing
20 changed files
with
1,745 additions
and
1,641 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,34 @@ | ||
package assert | ||
|
||
// TestingT is an interface wrapper for *testing.T | ||
type TestingT interface { | ||
Errorf(format string, args ...any) | ||
} | ||
|
||
// Predicate is a function that returns if a value meets a condition. | ||
type Predicate[T any] func(value T) bool | ||
|
||
// Condition is a function to assert a condition. | ||
type Condition[T any] func(value T) | ||
|
||
// ThatString starts assertions on a string. | ||
func ThatString(t TestingT, actual string) *StringAssert { | ||
if h, ok := t.(tHelper); ok { | ||
h.Helper() | ||
} | ||
return &StringAssert{t: t, actual: actual} | ||
stringAssert := &StringAssert{actual: actual} | ||
baseAssert := &BaseAssert[StringAssert]{t: t, info: NewWritableAssertionInfo(), a: stringAssert} | ||
stringAssert.BaseAssert = baseAssert | ||
return stringAssert | ||
} | ||
|
||
// ThatSlice starts assertions on a slice. | ||
func ThatSlice[E any](t TestingT, actual []E) *SliceAssert[E] { | ||
func ThatSlice[T ~[]E, E any](t TestingT, actual T) *SliceAssert[E] { | ||
if h, ok := t.(tHelper); ok { | ||
h.Helper() | ||
} | ||
sliceAssert := &SliceAssert[E]{actual: actual} | ||
baseAssert := &BaseAssert[SliceAssert[E]]{t: t, info: NewWritableAssertionInfo(), a: sliceAssert} | ||
sliceAssert.BaseAssert = baseAssert | ||
return sliceAssert | ||
} | ||
|
||
// ThatBool starts assertions on a bool. | ||
func ThatBool(t TestingT, actual bool) *BoolAssert { | ||
if h, ok := t.(tHelper); ok { | ||
h.Helper() | ||
} | ||
return &SliceAssert[E]{t: t, actual: actual} | ||
boolAssert := &BoolAssert{actual: actual} | ||
baseAssert := &BaseAssert[BoolAssert]{t: t, info: NewWritableAssertionInfo(), a: boolAssert} | ||
boolAssert.BaseAssert = baseAssert | ||
return boolAssert | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package assert | ||
|
||
type BaseAssert[T any] struct { | ||
t TestingT | ||
info *WritableAssertionInfo | ||
a *T | ||
} | ||
|
||
// DescribedAs sets an optional description for the following assertion. | ||
func (a *BaseAssert[T]) DescribedAs(description Description) *T { | ||
a.info.WithDescription(description) | ||
return a.a | ||
} | ||
|
||
// WithFailMessage overrides the default error message for the following assertions. | ||
func (a *BaseAssert[T]) WithFailMessage(message string, args ...any) *T { | ||
a.info.WithOverridingFailureMessage(message, args...) | ||
return a.a | ||
} | ||
|
||
// WithFailMessageSupplier overrides the default error message for the following assertions. | ||
// The new error message is built if the assertion fails by consuming the given Supplier function. | ||
func (a *BaseAssert[T]) WithFailMessageSupplier(supplier Supplier[string]) *T { | ||
a.info.WithOverridingFailureMessageSupplier(supplier) | ||
return a.a | ||
} | ||
|
||
// WithRepresentation uses the given representation to describe values in error messages. | ||
// | ||
// currencyRepresentation := func(value any) string { return fmt.Sprintf("€%d", value) } | ||
// | ||
// // assertion will fail with message: | ||
// // expected value to be zero, but got €42 | ||
// assert.ThatInt(t, 42). | ||
// WithRepresentation(currencyRepresentation). | ||
// IsZero() | ||
func (a *BaseAssert[T]) WithRepresentation(representation Representation) *T { | ||
a.info.UsingRepresentation(representation) | ||
return a.a | ||
} | ||
|
||
// InHexadecimal uses hexadecimal representation to describe values in error messages. | ||
func (a *BaseAssert[T]) InHexadecimal() *T { | ||
a.info.UsingHexadecimalRepresentation() | ||
return a.a | ||
} | ||
|
||
// InBinary uses binary representation to describe values in error messages. | ||
func (a *BaseAssert[T]) InBinary() *T { | ||
a.info.UsingBinaryRepresentation() | ||
return a.a | ||
} | ||
|
||
// FailWithMessage records an assertion error | ||
func (a *BaseAssert[T]) FailWithMessage(message string, args ...any) { | ||
if h, ok := a.t.(tHelper); ok { | ||
h.Helper() | ||
} | ||
description := a.info.Description() | ||
overridingErrorMessage := a.info.OverridingFailureMessage() | ||
representation := a.info.Representation() | ||
if overridingErrorMessage != "" { | ||
a.t.Errorf(messageFormatter.Format(description, representation, overridingErrorMessage, args...)) | ||
} else { | ||
a.t.Errorf(messageFormatter.Format(description, representation, message, args...)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package assert | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
type fixtureT struct { | ||
message string | ||
} | ||
|
||
func (f *fixtureT) Errorf(format string, args ...any) { | ||
f.message = fmt.Sprintf(format, args...) | ||
} | ||
|
||
func (f *fixtureT) Helper() {} | ||
|
||
type DummyAssert struct { | ||
*BaseAssert[DummyAssert] | ||
} | ||
|
||
func TestDescription(t *testing.T) { | ||
fixture := new(fixtureT) | ||
baseAssert := &BaseAssert[DummyAssert]{t: fixture, info: NewWritableAssertionInfo()} | ||
|
||
baseAssert.DescribedAs("hobbit") | ||
baseAssert.FailWithMessage("message") | ||
|
||
if !strings.Contains(fixture.message, "[hobbit]") { | ||
t.Errorf("expected assertion error message to contain description %s, but got %s", "[hobbit]", fixture.message) | ||
} | ||
} | ||
|
||
func TestOverridingFailMessage(t *testing.T) { | ||
fixture := new(fixtureT) | ||
baseAssert := &BaseAssert[DummyAssert]{t: fixture, info: NewWritableAssertionInfo()} | ||
|
||
baseAssert.WithFailMessage("overriding message") | ||
baseAssert.FailWithMessage("default message") | ||
|
||
if !strings.Contains(fixture.message, "overriding message") { | ||
t.Errorf("expected assertion error message to contain %s, but got %s", "overriding message", fixture.message) | ||
} | ||
} | ||
|
||
func TestOverridingFailMessageSupplier(t *testing.T) { | ||
fixture := new(fixtureT) | ||
baseAssert := &BaseAssert[DummyAssert]{t: fixture, info: NewWritableAssertionInfo()} | ||
|
||
baseAssert.WithFailMessageSupplier(func() string { return "overriding message" }) | ||
baseAssert.FailWithMessage("default message") | ||
|
||
if !strings.Contains(fixture.message, "overriding message") { | ||
t.Errorf("expected assertion error message to contain %s, but got %s", "overriding message", fixture.message) | ||
} | ||
} | ||
|
||
func TestRepresentation(t *testing.T) { | ||
fixture := new(fixtureT) | ||
baseAssert := &BaseAssert[DummyAssert]{t: fixture, info: NewWritableAssertionInfo()} | ||
|
||
currencyRepresentation := func(value any) string { return fmt.Sprintf("€%d", value) } | ||
baseAssert.WithRepresentation(currencyRepresentation) | ||
baseAssert.FailWithMessage("expected %s", 42) | ||
|
||
if !strings.Contains(fixture.message, "€42") { | ||
t.Errorf("expected assertion error message to contain %s, but got %s", "€42", fixture.message) | ||
} | ||
} | ||
|
||
func TestHexadecimalRepresentation(t *testing.T) { | ||
fixture := new(fixtureT) | ||
baseAssert := &BaseAssert[DummyAssert]{t: fixture, info: NewWritableAssertionInfo()} | ||
|
||
baseAssert.InHexadecimal() | ||
baseAssert.FailWithMessage("expected %s", 42) | ||
|
||
if !strings.Contains(fixture.message, "2A") { | ||
t.Errorf("expected assertion error message to contain %s, but got %s", "2A", fixture.message) | ||
} | ||
} | ||
|
||
func TestBinaryRepresentation(t *testing.T) { | ||
fixture := new(fixtureT) | ||
baseAssert := &BaseAssert[DummyAssert]{t: fixture, info: NewWritableAssertionInfo()} | ||
|
||
baseAssert.InBinary() | ||
baseAssert.FailWithMessage("expected %s", 42) | ||
|
||
if !strings.Contains(fixture.message, "101010") { | ||
t.Errorf("expected assertion error message to contain %s, but got %s", "101010", fixture.message) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package assert | ||
|
||
// BoolAssert provides assertions on boolean values. | ||
type BoolAssert struct { | ||
*BaseAssert[BoolAssert] | ||
actual bool | ||
} | ||
|
||
// IsTrue verifies that the actual value is true. | ||
// | ||
// // assertions will pass | ||
// assert.ThatBool(t, true).IsTrue() | ||
// | ||
// // assertions will fail | ||
// assert.ThatBool(t, false).IsTrue() | ||
func (a *BoolAssert) IsTrue() *BoolAssert { | ||
if h, ok := a.t.(tHelper); ok { | ||
h.Helper() | ||
} | ||
if !a.actual { | ||
a.FailWithMessage("expected value to be true, but got %s", a.actual) | ||
} | ||
return a | ||
} | ||
|
||
// IsFalse verifies that the actual value is true. | ||
// | ||
// // assertions will pass | ||
// assert.ThatBool(t, false).IsFalse() | ||
// | ||
// // assertions will fail | ||
// assert.ThatBool(t, true).IsFalse() | ||
func (a *BoolAssert) IsFalse() *BoolAssert { | ||
if h, ok := a.t.(tHelper); ok { | ||
h.Helper() | ||
} | ||
if a.actual { | ||
a.FailWithMessage("expected value to be false, but got %s", a.actual) | ||
} | ||
return a | ||
} | ||
|
||
// IsEqualTo verifies that the actual value is equal to the given one. | ||
// | ||
// // assertions will pass | ||
// assert.ThatBool(t, false).IsEqualTo(false) | ||
// | ||
// // assertions will fail | ||
// assert.ThatBool(t, true).IsEqualTo(false) | ||
func (a *BoolAssert) IsEqualTo(expected bool) *BoolAssert { | ||
if h, ok := a.t.(tHelper); ok { | ||
h.Helper() | ||
} | ||
if a.actual { | ||
a.FailWithMessage("expected value to be %s, but got %s", expected, a.actual) | ||
} | ||
return a | ||
} | ||
|
||
// IsNotEqualTo verifies that the actual value is not equal to the given one. | ||
// | ||
// // assertions will pass | ||
// assert.ThatBool(t, false).IsNotEqualTo(true) | ||
// | ||
// // assertions will fail | ||
// assert.ThatBool(t, true).IsNotEqualTo(true) | ||
func (a *BoolAssert) IsNotEqualTo(expected bool) *BoolAssert { | ||
if h, ok := a.t.(tHelper); ok { | ||
h.Helper() | ||
} | ||
if a.actual { | ||
a.FailWithMessage("expected value not to be %s, but got %s", expected, a.actual) | ||
} | ||
return a | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package assert_test | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/skhome/assertg/assert" | ||
) | ||
|
||
type boolTest struct { | ||
value bool | ||
expected bool | ||
ok bool | ||
} | ||
|
||
func TestBoolIsTrue(t *testing.T) { | ||
tests := []boolTest{ | ||
{value: true, ok: true}, | ||
{value: false, ok: false}, | ||
} | ||
messageFormat := "expected value to be true, but got <%t>" | ||
runTests(t, tests)(func(fixture *fixtureT, test boolTest) (bool, string) { | ||
assert.ThatBool(fixture, test.value).IsTrue() | ||
return test.ok, fmt.Sprintf(messageFormat, test.value) | ||
}) | ||
} | ||
|
||
func TestBoolIsFalse(t *testing.T) { | ||
tests := []boolTest{ | ||
{value: false, ok: true}, | ||
{value: true, ok: false}, | ||
} | ||
messageFormat := "expected value to be false, but got <%t>" | ||
runTests(t, tests)(func(fixture *fixtureT, test boolTest) (bool, string) { | ||
assert.ThatBool(fixture, test.value).IsFalse() | ||
return test.ok, fmt.Sprintf(messageFormat, test.value) | ||
}) | ||
} | ||
|
||
func TestBoolIsEqualTo(t *testing.T) { | ||
tests := []boolTest{ | ||
{value: false, expected: false, ok: true}, | ||
{value: true, expected: false, ok: false}, | ||
} | ||
messageFormat := "expected value to be <%t>, but got <%t>" | ||
runTests(t, tests)(func(fixture *fixtureT, test boolTest) (bool, string) { | ||
assert.ThatBool(fixture, test.value).IsEqualTo(test.expected) | ||
return test.ok, fmt.Sprintf(messageFormat, test.expected, test.value) | ||
}) | ||
} | ||
|
||
func TestBoolIsNotEqualTo(t *testing.T) { | ||
tests := []boolTest{ | ||
{value: false, expected: true, ok: true}, | ||
{value: true, expected: true, ok: false}, | ||
} | ||
messageFormat := "expected value not to be <%t>, but got <%t>" | ||
runTests(t, tests)(func(fixture *fixtureT, test boolTest) (bool, string) { | ||
assert.ThatBool(fixture, test.value).IsNotEqualTo(test.expected) | ||
return test.ok, fmt.Sprintf(messageFormat, test.expected, test.value) | ||
}) | ||
} |
Oops, something went wrong.