diff --git a/README.md b/README.md new file mode 100644 index 0000000..13afeab --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Hamcrest for GO \ No newline at end of file diff --git a/comparator/greater_than_or_equal_to.go b/comparator/greater_than_or_equal_to.go deleted file mode 100644 index 5cd0ced..0000000 --- a/comparator/greater_than_or_equal_to.go +++ /dev/null @@ -1,30 +0,0 @@ -package comparator - -import ( - "github.com/maurofran/hamcrest4go/constraints" - "github.com/maurofran/hamcrest4go/matcher" -) - -// GreaterThanOrEqualTo creates a new matcher for grater than comparison. -func GreaterThanOrEqualTo[T constraints.Ordered](value T) matcher.Matcher[T] { - return greaterThanOrEqualTo[T]{ref: value} -} - -type greaterThanOrEqualTo[T constraints.Ordered] struct { - ref T -} - -func (e greaterThanOrEqualTo[T]) Matches(value T) bool { - return value >= e.ref -} - -func (e greaterThanOrEqualTo[T]) DescribeTo(description matcher.Description) { - description.AppendText("a value greater than or equal to ") - description.AppendValue(e.ref) -} - -func (e greaterThanOrEqualTo[T]) DescribeMismatch(actual T, description matcher.Description) { - description.AppendValue(actual) - description.AppendText(" was greater than or equal to ") - description.AppendValue(e.ref) -} diff --git a/comparator/less_than_or_equal_to.go b/comparator/less_than_or_equal_to.go deleted file mode 100644 index 6e6712e..0000000 --- a/comparator/less_than_or_equal_to.go +++ /dev/null @@ -1,30 +0,0 @@ -package comparator - -import ( - "github.com/maurofran/hamcrest4go/constraints" - "github.com/maurofran/hamcrest4go/matcher" -) - -// LessThanOrEqualTo creates a new matcher for grater than comparison. -func LessThanOrEqualTo[T constraints.Ordered](value T) matcher.Matcher[T] { - return lessThanOrEqualTo[T]{ref: value} -} - -type lessThanOrEqualTo[T constraints.Ordered] struct { - ref T -} - -func (e lessThanOrEqualTo[T]) Matches(value T) bool { - return value <= e.ref -} - -func (e lessThanOrEqualTo[T]) DescribeTo(description matcher.Description) { - description.AppendText("a value lesser than to ") - description.AppendValue(e.ref) -} - -func (e lessThanOrEqualTo[T]) DescribeMismatch(actual T, description matcher.Description) { - description.AppendValue(actual) - description.AppendText(" was lesser than ") - description.AppendValue(e.ref) -} diff --git a/comparator/equals_to.go b/compare/equals_to.go similarity index 97% rename from comparator/equals_to.go rename to compare/equals_to.go index a3d9d86..3107fb3 100644 --- a/comparator/equals_to.go +++ b/compare/equals_to.go @@ -1,4 +1,4 @@ -package comparator +package compare import ( "github.com/maurofran/hamcrest4go/matcher" diff --git a/compare/equals_to_test.go b/compare/equals_to_test.go new file mode 100644 index 0000000..c7d10a2 --- /dev/null +++ b/compare/equals_to_test.go @@ -0,0 +1,51 @@ +package compare + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestEqualsTo_Matches(t *testing.T) { + tests := []struct { + name string + ref int + value int + expected bool + }{ + {"equals", 5, 5, true}, + {"lesser", 5, 4, false}, + {"greater", 5, 6, false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := EqualsTo(test.ref) + // When + actual := fixture.Matches(test.value) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestEqualsTo_DescribeTo(t *testing.T) { + // Given + fixture := EqualsTo(5) + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `a value equal to 5`, description.String()) +} + +func TestEqualsTo_DescribeMismatch(t *testing.T) { + // Given + fixture := EqualsTo(5) + description := matcher.StringDescription() + // When + fixture.DescribeMismatch(8, description) + // Then + assert.Equal(t, `8 was equal to 5`, description.String()) +} diff --git a/comparator/greater_than.go b/compare/greater_than.go similarity index 97% rename from comparator/greater_than.go rename to compare/greater_than.go index f928ec4..bd5fe8a 100644 --- a/comparator/greater_than.go +++ b/compare/greater_than.go @@ -1,4 +1,4 @@ -package comparator +package compare import ( "github.com/maurofran/hamcrest4go/constraints" diff --git a/compare/greater_than_or_equals_to.go b/compare/greater_than_or_equals_to.go new file mode 100644 index 0000000..e867a7f --- /dev/null +++ b/compare/greater_than_or_equals_to.go @@ -0,0 +1,30 @@ +package compare + +import ( + "github.com/maurofran/hamcrest4go/constraints" + "github.com/maurofran/hamcrest4go/matcher" +) + +// GreaterThanOrEqualsTo creates a new matcher for grater than comparison. +func GreaterThanOrEqualsTo[T constraints.Ordered](value T) matcher.Matcher[T] { + return greaterThanOrEqualsTo[T]{ref: value} +} + +type greaterThanOrEqualsTo[T constraints.Ordered] struct { + ref T +} + +func (e greaterThanOrEqualsTo[T]) Matches(value T) bool { + return value >= e.ref +} + +func (e greaterThanOrEqualsTo[T]) DescribeTo(description matcher.Description) { + description.AppendText("a value greater than or equals to ") + description.AppendValue(e.ref) +} + +func (e greaterThanOrEqualsTo[T]) DescribeMismatch(actual T, description matcher.Description) { + description.AppendValue(actual) + description.AppendText(" was greater than or equals to ") + description.AppendValue(e.ref) +} diff --git a/compare/greater_than_or_equals_to_test.go b/compare/greater_than_or_equals_to_test.go new file mode 100644 index 0000000..b4aeb61 --- /dev/null +++ b/compare/greater_than_or_equals_to_test.go @@ -0,0 +1,51 @@ +package compare + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGreaterThanOrEqualsTo_Matches(t *testing.T) { + tests := []struct { + name string + ref int + value int + expected bool + }{ + {"equals", 5, 5, true}, + {"lesser", 5, 4, false}, + {"greater", 5, 6, true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := GreaterThanOrEqualsTo(test.ref) + // When + actual := fixture.Matches(test.value) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestGreaterOrEqualsToThan_DescribeTo(t *testing.T) { + // Given + fixture := GreaterThanOrEqualsTo(5) + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `a value greater than or equals to 5`, description.String()) +} + +func TestGreaterThanOrEqualsTo_DescribeMismatch(t *testing.T) { + // Given + fixture := GreaterThanOrEqualsTo(5) + description := matcher.StringDescription() + // When + fixture.DescribeMismatch(8, description) + // Then + assert.Equal(t, `8 was greater than or equals to 5`, description.String()) +} diff --git a/compare/greater_than_test.go b/compare/greater_than_test.go new file mode 100644 index 0000000..3916366 --- /dev/null +++ b/compare/greater_than_test.go @@ -0,0 +1,51 @@ +package compare + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGreaterThan_Matches(t *testing.T) { + tests := []struct { + name string + ref int + value int + expected bool + }{ + {"equals", 5, 5, false}, + {"lesser", 5, 4, false}, + {"greater", 5, 6, true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := GreaterThan(test.ref) + // When + actual := fixture.Matches(test.value) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestGreaterThan_DescribeTo(t *testing.T) { + // Given + fixture := GreaterThan(5) + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `a value greater than 5`, description.String()) +} + +func TestGreaterThan_DescribeMismatch(t *testing.T) { + // Given + fixture := GreaterThan(5) + description := matcher.StringDescription() + // When + fixture.DescribeMismatch(8, description) + // Then + assert.Equal(t, `8 was greater than 5`, description.String()) +} diff --git a/comparator/less_than.go b/compare/less_than.go similarity index 90% rename from comparator/less_than.go rename to compare/less_than.go index d5845e4..b826eaf 100644 --- a/comparator/less_than.go +++ b/compare/less_than.go @@ -1,4 +1,4 @@ -package comparator +package compare import ( "github.com/maurofran/hamcrest4go/constraints" @@ -19,7 +19,7 @@ func (e lessThan[T]) Matches(value T) bool { } func (e lessThan[T]) DescribeTo(description matcher.Description) { - description.AppendText("a value lesser than to ") + description.AppendText("a value lesser than ") description.AppendValue(e.ref) } diff --git a/compare/less_than_or_equals_to.go b/compare/less_than_or_equals_to.go new file mode 100644 index 0000000..b5aaffa --- /dev/null +++ b/compare/less_than_or_equals_to.go @@ -0,0 +1,30 @@ +package compare + +import ( + "github.com/maurofran/hamcrest4go/constraints" + "github.com/maurofran/hamcrest4go/matcher" +) + +// LessThanOrEqualsTo creates a new matcher for grater than comparison. +func LessThanOrEqualsTo[T constraints.Ordered](value T) matcher.Matcher[T] { + return lessThanOrEqualsTo[T]{ref: value} +} + +type lessThanOrEqualsTo[T constraints.Ordered] struct { + ref T +} + +func (e lessThanOrEqualsTo[T]) Matches(value T) bool { + return value <= e.ref +} + +func (e lessThanOrEqualsTo[T]) DescribeTo(description matcher.Description) { + description.AppendText("a value less than or equals to ") + description.AppendValue(e.ref) +} + +func (e lessThanOrEqualsTo[T]) DescribeMismatch(actual T, description matcher.Description) { + description.AppendValue(actual) + description.AppendText(" was less than or equals to ") + description.AppendValue(e.ref) +} diff --git a/compare/less_than_or_equals_to_test.go b/compare/less_than_or_equals_to_test.go new file mode 100644 index 0000000..9f5d7e7 --- /dev/null +++ b/compare/less_than_or_equals_to_test.go @@ -0,0 +1,51 @@ +package compare + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestLessThanOrEqualsTo_Matches(t *testing.T) { + tests := []struct { + name string + ref int + value int + expected bool + }{ + {"equals", 5, 5, true}, + {"lesser", 5, 4, true}, + {"greater", 5, 6, false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := LessThanOrEqualsTo(test.ref) + // When + actual := fixture.Matches(test.value) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestLessOrEqualsToThan_DescribeTo(t *testing.T) { + // Given + fixture := LessThanOrEqualsTo(5) + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `a value less than or equals to 5`, description.String()) +} + +func TestLessThanOrEqualsTo_DescribeMismatch(t *testing.T) { + // Given + fixture := LessThanOrEqualsTo(5) + description := matcher.StringDescription() + // When + fixture.DescribeMismatch(8, description) + // Then + assert.Equal(t, `8 was less than or equals to 5`, description.String()) +} diff --git a/compare/less_than_test.go b/compare/less_than_test.go new file mode 100644 index 0000000..736c6e4 --- /dev/null +++ b/compare/less_than_test.go @@ -0,0 +1,51 @@ +package compare + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestLessThan_Matches(t *testing.T) { + tests := []struct { + name string + ref int + value int + expected bool + }{ + {"equals", 5, 5, false}, + {"lesser", 5, 4, true}, + {"greater", 5, 6, false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := LessThan(test.ref) + // When + actual := fixture.Matches(test.value) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestLesserThan_DescribeTo(t *testing.T) { + // Given + fixture := LessThan(5) + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `a value lesser than 5`, description.String()) +} + +func TestLesserThan_DescribeMismatch(t *testing.T) { + // Given + fixture := LessThan(5) + description := matcher.StringDescription() + // When + fixture.DescribeMismatch(8, description) + // Then + assert.Equal(t, `8 was lesser than 5`, description.String()) +} diff --git a/constraints/constraints.go b/constraints/constraints.go index 2782689..bd8cee0 100644 --- a/constraints/constraints.go +++ b/constraints/constraints.go @@ -16,6 +16,10 @@ type Float interface { ~float32 | ~float64 } +type Number interface { + Integer | Float +} + type Ordered interface { - Integer | Float | ~string + Number | ~string } diff --git a/go.mod b/go.mod index 1228fcc..63895a3 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module github.com/maurofran/hamcrest4go go 1.18 + +require ( + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.1.0 // indirect + github.com/stretchr/testify v1.7.1 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9f0da9a --- /dev/null +++ b/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/is/equal.go b/is/equal_to.go similarity index 92% rename from is/equal.go rename to is/equal_to.go index b8ee21e..0c72158 100644 --- a/is/equal.go +++ b/is/equal_to.go @@ -3,8 +3,8 @@ package is import "github.com/maurofran/hamcrest4go/matcher" // Equaler is the constraint interface for an object that has the Equal method. -type Equaler[O any] interface { - Equal(other O) bool +type Equaler[T any] interface { + Equal(other T) bool } // EqualTo gets a matcher for nil value. diff --git a/is/equal_to_test.go b/is/equal_to_test.go new file mode 100644 index 0000000..3dc5aa2 --- /dev/null +++ b/is/equal_to_test.go @@ -0,0 +1,61 @@ +package is + +import ( + "fmt" + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +type equalerImpl string + +func (e equalerImpl) Equal(other equalerImpl) bool { + return string(e) == string(other) +} + +func (e equalerImpl) String() string { + return fmt.Sprintf("%q", string(e)) +} + +func TestEqualTo_Matches(t *testing.T) { + tests := []struct { + name string + value equalerImpl + other equalerImpl + expected bool + }{ + {"not equals", "foo", "baz", false}, + {"equals", "foo", "foo", true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := EqualTo(test.other) + // When + actual := fixture.Matches(test.value) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestEqualTo_DescribeTo(t *testing.T) { + // Given + fixture := EqualTo(equalerImpl("foo")) + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `equal to "foo"`, description.String()) +} + +func TestEqualTo_DescribeMismatch(t *testing.T) { + // Given + fixture := EqualTo(equalerImpl("baz")) + description := matcher.StringDescription() + // When + fixture.DescribeMismatch("foo", description) + // Then + assert.Equal(t, `was "foo"`, description.String()) +} diff --git a/is/nil_or_zero.go b/is/nil_or_zero.go index a02ef1e..b6fa3fa 100644 --- a/is/nil_or_zero.go +++ b/is/nil_or_zero.go @@ -21,7 +21,7 @@ func (nilOrZero[T]) Matches(value T) bool { } func (nilOrZero[T]) DescribeTo(description matcher.Description) { - description.AppendText("nilOrZero") + description.AppendText("nil or zero value") } func (nilOrZero[T]) DescribeMismatch(actual T, description matcher.Description) { diff --git a/is/nil_or_zero_test.go b/is/nil_or_zero_test.go new file mode 100644 index 0000000..70628e2 --- /dev/null +++ b/is/nil_or_zero_test.go @@ -0,0 +1,91 @@ +package is + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNilOrZero_Matches(t *testing.T) { + tests := []struct { + name string + value string + expected bool + }{ + {"zero", "", true}, + {"not zero", "foo", false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := NilOrZero[string]() + // When + actual := fixture.Matches(test.value) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestNilOrZero_DescribeTo(t *testing.T) { + // Given + fixture := NilOrZero[string]() + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `nil or zero value`, description.String()) +} + +func TestNilOrZero_DescribeMismatch(t *testing.T) { + // Given + fixture := NilOrZero[string]() + description := matcher.StringDescription() + // When + fixture.DescribeMismatch("foo", description) + // Then + assert.Equal(t, `was "foo"`, description.String()) +} + +func TestNotNilOrZero_Matches(t *testing.T) { + tests := []struct { + name string + value string + expected bool + }{ + {"zero", "", false}, + {"not zero", "foo", true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := NotNilOrZero[string]() + // When + actual := fixture.Matches(test.value) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestNotNilOrZero_DescribeTo(t *testing.T) { + // Given + fixture := NotNilOrZero[string]() + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `not nil or zero value`, description.String()) +} + +func TestNotNilOrZero_DescribeMismatch(t *testing.T) { + // Given + fixture := NilOrZero[string]() + description := matcher.StringDescription() + // When + fixture.DescribeMismatch("foo", description) + // Then + assert.Equal(t, `was "foo"`, description.String()) +} diff --git a/is/not_test.go b/is/not_test.go new file mode 100644 index 0000000..edd0c63 --- /dev/null +++ b/is/not_test.go @@ -0,0 +1,62 @@ +package is + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +type mockMatcher struct { + mock.Mock +} + +func (m *mockMatcher) Matches(value string) bool { + args := m.Called(value) + return args.Bool(0) +} + +func (m *mockMatcher) DescribeTo(description matcher.Description) { + _ = m.Called(description) +} + +func (m *mockMatcher) DescribeMismatch(actual string, description matcher.Description) { + _ = m.Called(actual, description) +} + +func TestNot_Matches(t *testing.T) { + // Given + m := new(mockMatcher) + fixture := Not[string](m) + // When + m.On("Matches", "foo").Return(true) + actual := fixture.Matches("foo") + // Then + assert.Equal(t, false, actual) + m.AssertExpectations(t) +} + +func TestNot_DescribeTo(t *testing.T) { + // Given + m := new(mockMatcher) + fixture := Not[string](m) + description := matcher.StringDescription() + // When + m.On("DescribeTo", description) + fixture.DescribeTo(description) + // Then + assert.Equal(t, `not `, description.String()) + m.AssertExpectations(t) +} + +func TestNot_DescribeMismatch(t *testing.T) { + // Given + m := new(mockMatcher) + fixture := Not[string](m) + description := matcher.StringDescription() + // When + m.On("DescribeMismatch", "foo", description) + fixture.DescribeMismatch("foo", description) + // Then + m.AssertExpectations(t) +} diff --git a/matcher/base.go b/matcher/base.go new file mode 100644 index 0000000..6d82339 --- /dev/null +++ b/matcher/base.go @@ -0,0 +1,9 @@ +package matcher + +type Base struct { +} + +func (Base) DescribeMismatch(actual string, description Description) { + description.AppendText("was ") + description.AppendValue(actual) +} diff --git a/matcher/custom.go b/matcher/custom.go new file mode 100644 index 0000000..5575661 --- /dev/null +++ b/matcher/custom.go @@ -0,0 +1,30 @@ +package matcher + +// Fn is the type of the matcher function. +type Fn[T any] func(value T) bool + +// Custom creates a custom Matcher with supplied description and matcher function. +func Custom[T any](fixedDescription string, matchesFn Fn[T]) Matcher[T] { + return custom[T]{ + fn: matchesFn, + fixedDescription: fixedDescription, + } +} + +type custom[T any] struct { + fn Fn[T] + fixedDescription string +} + +func (c custom[T]) Matches(value T) bool { + return c.fn(value) +} + +func (custom[T]) DescribeMismatch(actual T, description Description) { + description.AppendText("was ") + description.AppendValue(actual) +} + +func (c custom[T]) DescribeTo(description Description) { + description.AppendText(c.fixedDescription) +} diff --git a/matcher/custom_test.go b/matcher/custom_test.go new file mode 100644 index 0000000..a2e9eb3 --- /dev/null +++ b/matcher/custom_test.go @@ -0,0 +1,44 @@ +package matcher + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestCustom_Matches(t *testing.T) { + // Given + fn := func(value string) bool { + return "foo" == value + } + fixture := Custom("description", fn) + // When + actual := fixture.Matches("foo") + // Then + assert.True(t, actual) +} + +func TestCustom_DescribeTo(t *testing.T) { + // Given + fn := func(value string) bool { + return false + } + fixture := Custom("description", fn) + // When + description := StringDescription() + fixture.DescribeTo(description) + // Then + assert.Equal(t, "description", description.String()) +} + +func TestCustom_DescribeMismatch(t *testing.T) { + // Given + fn := func(value string) bool { + return false + } + fixture := Custom("description", fn) + // When + description := StringDescription() + fixture.DescribeMismatch("actual", description) + // Then + assert.Equal(t, `was "actual"`, description.String()) +} diff --git a/matcher/description.go b/matcher/description.go index 6937d72..4b64440 100644 --- a/matcher/description.go +++ b/matcher/description.go @@ -28,12 +28,6 @@ type SelfDescribing interface { DescribeTo(description Description) } -func toString(value SelfDescribing) string { - description := StringDescription() - description.AppendDescriptionOf(value) - return description.String() -} - // Description is the representation of a matcher error description. type Description struct { writer StringWriter @@ -90,13 +84,13 @@ func (d Description) AppendValue(value interface{}) { d.append(toGoSyntax(str)) } else if r, ok := value.(rune); ok { d.append(toGoSyntax(string([]rune{r}))) + } else if sd, ok := value.(SelfDescribing); ok { + sd.DescribeTo(d) } else if reflect.TypeOf(value).Kind() == reflect.Slice || reflect.TypeOf(value).Kind() == reflect.Array { slice := reflect.ValueOf(value) d.appendSlice("[", ", ", "]", slice) } else { - d.append("<") d.append(descriptionOf(value)) - d.append(">") } } @@ -130,7 +124,13 @@ func (d Description) String() string { } func descriptionOf(value interface{}) string { - return fmt.Sprintf("%s", value) + switch value.(type) { + case string: + return fmt.Sprintf("%q", value) + case fmt.Stringer: + return fmt.Sprintf("%s", value) + } + return fmt.Sprintf("%v", value) } func toGoSyntax(value string) string { diff --git a/matcher/description_test.go b/matcher/description_test.go new file mode 100644 index 0000000..d1c6374 --- /dev/null +++ b/matcher/description_test.go @@ -0,0 +1,86 @@ +package matcher + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +type MockSelfDescribing struct { + mock.Mock +} + +func (m *MockSelfDescribing) DescribeTo(description Description) { + m.Called(description) +} + +func TestStringDescription_AppendText(t *testing.T) { + // Given + fixture := StringDescription() + // When + fixture.AppendText("a text") + // Then + assert.NoError(t, fixture.Err()) + assert.Equal(t, "a text", fixture.String()) +} + +func TestStringDescription_AppendDescriptionOf(t *testing.T) { + // Given + fixture := StringDescription() + value := new(MockSelfDescribing) + value.On("DescribeTo", fixture) + // When + fixture.AppendDescriptionOf(value) + // Then + value.AssertExpectations(t) +} + +func TestStringDescription_AppendValue(t *testing.T) { + tests := []struct { + name string + value interface{} + expected string + }{ + {"nil value", nil, "nil"}, + {"empty string", "", `""`}, + {"string with value", "a value", `"a value"`}, + {"a rune", rune(65), `"A"`}, + {"an empty slice", []interface{}{}, `[]`}, + {"a slice with values", []int{1, 2, 3}, `[1, 2, 3]`}, + {"an empty array", [...]interface{}{}, `[]`}, + {"an array with values", [...]interface{}{"a", "b"}, `["a", "b"]`}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := StringDescription() + // When + fixture.AppendValue(test.value) + // Then + assert.NoError(t, fixture.Err()) + assert.Equal(t, test.expected, fixture.String()) + }) + } + + t.Run("a self describing value", func(t *testing.T) { + // Given + fixture := StringDescription() + value := new(MockSelfDescribing) + // When + value.On("DescribeTo", fixture) + fixture.AppendValue(value) + // Then + value.AssertExpectations(t) + }) + + t.Run("a slice of self describing values", func(t *testing.T) { + // Given + fixture := StringDescription() + value := new(MockSelfDescribing) + // When + value.On("DescribeTo", fixture) + fixture.AppendValue([]interface{}{value}) + // Then + value.AssertExpectations(t) + }) +} diff --git a/matcher/matcher.go b/matcher/matcher.go index d426032..c7f93d6 100644 --- a/matcher/matcher.go +++ b/matcher/matcher.go @@ -1,40 +1,8 @@ package matcher -// Fn is the type of the matcher function. -type Fn[T any] func(value T) bool - +// Matcher is the interface describing a generic matcher. type Matcher[T any] interface { SelfDescribing Matches(value T) bool DescribeMismatch(actual T, description Description) } - -type matcher[T any] struct { - fn Fn[T] -} - -func (m matcher[T]) Matches(value T) bool { - return m.fn(value) -} - -func (matcher[T]) DescribeMismatch(actual T, description Description) { - description.AppendText("was ") - description.AppendValue(actual) -} - -// Custom creates a custom Matcher with supplied description and matcher function. -func Custom[T any](fixedDescription string, matchesFn Fn[T]) Matcher[T] { - return custom[T]{ - matcher: matcher[T]{fn: matchesFn}, - fixedDescription: fixedDescription, - } -} - -type custom[T any] struct { - matcher[T] - fixedDescription string -} - -func (c custom[T]) DescribeTo(description Description) { - description.AppendText(c.fixedDescription) -} diff --git a/text/contains.go b/text/contains.go index daffe1c..041a4e7 100644 --- a/text/contains.go +++ b/text/contains.go @@ -11,6 +11,7 @@ func Contains(substr string) matcher.Matcher[string] { } type contains struct { + matcher.Base substr string } @@ -19,12 +20,7 @@ func (m contains) Matches(value string) bool { } func (m contains) DescribeTo(description matcher.Description) { - description.AppendText("a string containing '") + description.AppendText("a string containing \"") description.AppendText(m.substr) - description.AppendText("'") -} - -func (contains) DescribeMismatch(actual string, description matcher.Description) { - description.AppendText("was ") - description.AppendValue(actual) + description.AppendText("\"") } diff --git a/text/contains_test.go b/text/contains_test.go new file mode 100644 index 0000000..681842b --- /dev/null +++ b/text/contains_test.go @@ -0,0 +1,50 @@ +package text + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestContains_Matches(t *testing.T) { + tests := []struct { + name string + substr string + test string + expected bool + }{ + {"not contains", "foo", "search inside me", false}, + {"contains", "inside", "search inside me", true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := Contains(test.substr) + // When + actual := fixture.Matches(test.test) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestContains_DescribeTo(t *testing.T) { + // Given + fixture := Contains("inside") + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `a string containing "inside"`, description.String()) +} + +func TestContains_DescribeMismatch(t *testing.T) { + // Given + fixture := Contains("inside") + description := matcher.StringDescription() + // When + fixture.DescribeMismatch("foo", description) + // Then + assert.Equal(t, `was "foo"`, description.String()) +} diff --git a/text/has_prefix.go b/text/has_prefix.go index 0a2dca4..3ca9912 100644 --- a/text/has_prefix.go +++ b/text/has_prefix.go @@ -11,6 +11,7 @@ func HasPrefix(prefix string) matcher.Matcher[string] { } type hasPrefix struct { + matcher.Base prefix string } @@ -19,12 +20,7 @@ func (m hasPrefix) Matches(value string) bool { } func (m hasPrefix) DescribeTo(description matcher.Description) { - description.AppendText("a string with suffix '") + description.AppendText("a string with prefix \"") description.AppendText(m.prefix) - description.AppendText("'") -} - -func (hasPrefix) DescribeMismatch(actual string, description matcher.Description) { - description.AppendText("was ") - description.AppendValue(actual) + description.AppendText("\"") } diff --git a/text/has_prefix_test.go b/text/has_prefix_test.go new file mode 100644 index 0000000..6d70364 --- /dev/null +++ b/text/has_prefix_test.go @@ -0,0 +1,50 @@ +package text + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestHasPrefix_Matches(t *testing.T) { + tests := []struct { + name string + substr string + test string + expected bool + }{ + {"not has prefix", "foo", "has prefix", false}, + {"has prefix", "has", "has prefix", true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := HasPrefix(test.substr) + // When + actual := fixture.Matches(test.test) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestHasPrefix_DescribeTo(t *testing.T) { + // Given + fixture := HasPrefix("inside") + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `a string with prefix "inside"`, description.String()) +} + +func TestHasPrefix_DescribeMismatch(t *testing.T) { + // Given + fixture := HasPrefix("inside") + description := matcher.StringDescription() + // When + fixture.DescribeMismatch("foo", description) + // Then + assert.Equal(t, `was "foo"`, description.String()) +} diff --git a/text/has_suffix.go b/text/has_suffix.go index 3d8d7c6..d24d847 100644 --- a/text/has_suffix.go +++ b/text/has_suffix.go @@ -11,6 +11,7 @@ func HasSuffix(suffix string) matcher.Matcher[string] { } type hasSuffix struct { + matcher.Base suffix string } @@ -19,12 +20,7 @@ func (m hasSuffix) Matches(value string) bool { } func (m hasSuffix) DescribeTo(description matcher.Description) { - description.AppendText("a string with suffix '") + description.AppendText("a string with suffix \"") description.AppendText(m.suffix) - description.AppendText("'") -} - -func (hasSuffix) DescribeMismatch(actual string, description matcher.Description) { - description.AppendText("was ") - description.AppendValue(actual) + description.AppendText("\"") } diff --git a/text/has_suffix_test.go b/text/has_suffix_test.go new file mode 100644 index 0000000..e79b32c --- /dev/null +++ b/text/has_suffix_test.go @@ -0,0 +1,50 @@ +package text + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestHasSuffix_Matches(t *testing.T) { + tests := []struct { + name string + substr string + test string + expected bool + }{ + {"not has suffix", "foo", "has suffix", false}, + {"has suffix", "fix", "has suffix", true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := HasSuffix(test.substr) + // When + actual := fixture.Matches(test.test) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestHasSuffix_DescribeTo(t *testing.T) { + // Given + fixture := HasSuffix("inside") + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `a string with suffix "inside"`, description.String()) +} + +func TestHasSuffix_DescribeMismatch(t *testing.T) { + // Given + fixture := HasPrefix("inside") + description := matcher.StringDescription() + // When + fixture.DescribeMismatch("foo", description) + // Then + assert.Equal(t, `was "foo"`, description.String()) +} diff --git a/text/is_blank.go b/text/is_blank.go index b654cd4..7332054 100644 --- a/text/is_blank.go +++ b/text/is_blank.go @@ -1,18 +1,25 @@ package text import ( + "github.com/maurofran/hamcrest4go/is" "github.com/maurofran/hamcrest4go/matcher" "strings" ) -// IsBlank matches blank strings +// IsBlank matches blank strings. func IsBlank() matcher.Matcher[string] { return isBlankInstance } +// IsNotBlank matches not blank strings. +func IsNotBlank() matcher.Matcher[string] { + return is.Not(IsBlank()) +} + var isBlankInstance = isBlank{} type isBlank struct { + matcher.Base } func (isBlank) Matches(value string) bool { @@ -22,8 +29,3 @@ func (isBlank) Matches(value string) bool { func (isBlank) DescribeTo(description matcher.Description) { description.AppendText("a blank string") } - -func (isBlank) DescribeMismatch(actual string, description matcher.Description) { - description.AppendText("was ") - description.AppendValue(actual) -} diff --git a/text/is_blank_test.go b/text/is_blank_test.go new file mode 100644 index 0000000..8db9d26 --- /dev/null +++ b/text/is_blank_test.go @@ -0,0 +1,93 @@ +package text + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestIsBlank_Matches(t *testing.T) { + tests := []struct { + name string + test string + expected bool + }{ + {"blank string", " ", true}, + {"empty string", "", true}, + {"not blank string", "foo", false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := IsBlank() + // When + actual := fixture.Matches(test.test) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestIsBlank_DescribeTo(t *testing.T) { + // Given + fixture := IsBlank() + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `a blank string`, description.String()) +} + +func TestIsBlank_DescribeMismatch(t *testing.T) { + // Given + fixture := IsBlank() + description := matcher.StringDescription() + // When + fixture.DescribeMismatch("foo", description) + // Then + assert.Equal(t, `was "foo"`, description.String()) +} + +func TestIsNotBlank_Matches(t *testing.T) { + tests := []struct { + name string + test string + expected bool + }{ + {"blank string", " ", false}, + {"empty string", "", false}, + {"not blank string", "foo", true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := IsNotBlank() + // When + actual := fixture.Matches(test.test) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestIsNotBlank_DescribeTo(t *testing.T) { + // Given + fixture := IsNotBlank() + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `not a blank string`, description.String()) +} + +func TestIsNotBlank_DescribeMismatch(t *testing.T) { + // Given + fixture := IsNotBlank() + description := matcher.StringDescription() + // When + fixture.DescribeMismatch("foo", description) + // Then + assert.Equal(t, `was "foo"`, description.String()) +} diff --git a/text/is_empty.go b/text/is_empty.go index c508534..8286b6f 100644 --- a/text/is_empty.go +++ b/text/is_empty.go @@ -1,15 +1,24 @@ package text -import "github.com/maurofran/hamcrest4go/matcher" +import ( + "github.com/maurofran/hamcrest4go/is" + "github.com/maurofran/hamcrest4go/matcher" +) -// IsEmpty matches empty strings +// IsEmpty matches empty strings. func IsEmpty() matcher.Matcher[string] { return isEmptyInstance } +// IsNotEmpty matches not empty strings. +func IsNotEmpty() matcher.Matcher[string] { + return is.Not(IsEmpty()) +} + var isEmptyInstance = isEmpty{} type isEmpty struct { + matcher.Base } func (isEmpty) Matches(value string) bool { @@ -19,8 +28,3 @@ func (isEmpty) Matches(value string) bool { func (isEmpty) DescribeTo(description matcher.Description) { description.AppendText("an empty string") } - -func (isEmpty) DescribeMismatch(actual string, description matcher.Description) { - description.AppendText("was ") - description.AppendValue(actual) -} diff --git a/text/is_empty_test.go b/text/is_empty_test.go new file mode 100644 index 0000000..131b2ed --- /dev/null +++ b/text/is_empty_test.go @@ -0,0 +1,93 @@ +package text + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestIsEmpty_Matches(t *testing.T) { + tests := []struct { + name string + test string + expected bool + }{ + {"blank string", " ", false}, + {"empty string", "", true}, + {"not empty string", "foo", false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := IsEmpty() + // When + actual := fixture.Matches(test.test) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestIsEmpty_DescribeTo(t *testing.T) { + // Given + fixture := IsEmpty() + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `an empty string`, description.String()) +} + +func TestIsEmpty_DescribeMismatch(t *testing.T) { + // Given + fixture := IsEmpty() + description := matcher.StringDescription() + // When + fixture.DescribeMismatch("foo", description) + // Then + assert.Equal(t, `was "foo"`, description.String()) +} + +func TestIsNotEmpty_Matches(t *testing.T) { + tests := []struct { + name string + test string + expected bool + }{ + {"blank string", " ", true}, + {"empty string", "", false}, + {"not empty string", "foo", true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := IsNotEmpty() + // When + actual := fixture.Matches(test.test) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestIsNotEmpty_DescribeTo(t *testing.T) { + // Given + fixture := IsNotEmpty() + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `not an empty string`, description.String()) +} + +func TestIsNotEmpty_DescribeMismatch(t *testing.T) { + // Given + fixture := IsNotEmpty() + description := matcher.StringDescription() + // When + fixture.DescribeMismatch("foo", description) + // Then + assert.Equal(t, `was "foo"`, description.String()) +} diff --git a/text/length.go b/text/length.go index cc8f094..4479140 100644 --- a/text/length.go +++ b/text/length.go @@ -1,14 +1,13 @@ package text import ( - "github.com/maurofran/hamcrest4go/comparator" + "github.com/maurofran/hamcrest4go/compare" "github.com/maurofran/hamcrest4go/matcher" - "strings" ) // HasExactLength check if given text has given exact length. func HasExactLength(length int) matcher.Matcher[string] { - return HasLength(comparator.EqualsTo(length)) + return HasLength(compare.EqualsTo(length)) } // HasLength check if given text has length matching the supplied matcher. @@ -17,19 +16,15 @@ func HasLength(lengthMatcher matcher.Matcher[int]) matcher.Matcher[string] { } type hasLength struct { + matcher.Base lengthMatcher matcher.Matcher[int] } func (l hasLength) Matches(value string) bool { - return strings.TrimSpace(value) == "" + return l.lengthMatcher.Matches(len(value)) } func (l hasLength) DescribeTo(description matcher.Description) { - description.AppendText("a string with length ") + description.AppendText("a string with length with ") description.AppendDescriptionOf(l.lengthMatcher) } - -func (hasLength) DescribeMismatch(actual string, description matcher.Description) { - description.AppendText("was ") - description.AppendValue(actual) -} diff --git a/text/length_test.go b/text/length_test.go new file mode 100644 index 0000000..423a94f --- /dev/null +++ b/text/length_test.go @@ -0,0 +1,50 @@ +package text + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestHasExactLength_Matches(t *testing.T) { + tests := []struct { + name string + length int + test string + expected bool + }{ + {"equal length", 3, "foo", true}, + {"different length", 3, "fooBar", false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := HasExactLength(test.length) + // When + actual := fixture.Matches(test.test) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestHasExactLength_DescribeTo(t *testing.T) { + // Given + fixture := HasExactLength(3) + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `a string with length with a value equal to 3`, description.String()) +} + +func TestHasExactLength_DescribeMismatch(t *testing.T) { + // Given + fixture := IsBlank() + description := matcher.StringDescription() + // When + fixture.DescribeMismatch("foo", description) + // Then + assert.Equal(t, `was "foo"`, description.String()) +} diff --git a/text/matches.go b/text/matches.go index 05f0e44..90400ee 100644 --- a/text/matches.go +++ b/text/matches.go @@ -24,9 +24,9 @@ func (m matches) Matches(value string) bool { } func (m matches) DescribeTo(description matcher.Description) { - description.AppendText("a string matching the pattern '") + description.AppendText("a string matching the pattern \"") description.AppendText(m.pattern.String()) - description.AppendText("'") + description.AppendText("\"") } func (matches) DescribeMismatch(actual string, description matcher.Description) { diff --git a/text/matches_test.go b/text/matches_test.go new file mode 100644 index 0000000..3b054a7 --- /dev/null +++ b/text/matches_test.go @@ -0,0 +1,52 @@ +package text + +import ( + "github.com/maurofran/hamcrest4go/matcher" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestMatchesPattern_Matches(t *testing.T) { + tests := []struct { + name string + pattern string + test string + expected bool + }{ + {"blank string", "\\d+", " ", false}, + {"empty string", "\\d+", "", false}, + {"not empty string", "\\d+", "foo", false}, + {"matching string", "\\d+", "123", true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Given + fixture := MatchesPattern(test.pattern) + // When + actual := fixture.Matches(test.test) + // Then + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestMatchesPattern_DescribeTo(t *testing.T) { + // Given + fixture := MatchesPattern("\\d+") + description := matcher.StringDescription() + // When + fixture.DescribeTo(description) + // Then + assert.Equal(t, `a string matching the pattern "\d+"`, description.String()) +} + +func TestMatchesPattern_DescribeMismatch(t *testing.T) { + // Given + fixture := MatchesPattern("\\d+") + description := matcher.StringDescription() + // When + fixture.DescribeMismatch("foo", description) + // Then + assert.Equal(t, `was "foo"`, description.String()) +}