Open therealmitchconnors opened 1 year ago
I and others have explored using generics a bit - and while the compile-time feedback is certainly helpful, in practice I find that it doesn't solve as many problems and can make composition a bit harder.
Instead, Gomega has fully embraced reflect
ion and been evolving towards some pretty nifty composition. As a concrete example, the test in your example can be written in the following ways:
type Object struct {
Owner OtherObject
}
func getObjects() []Object {...}
// simple composition of matchers using `HaveField` and `BeNil`
func TestSomethingViaHaveField(t *testing.T) {
g := NewGomegaWithT(t)
g.Expect(getObjects()).To(HaveEach(HaveField("Owner", BeNil()))
}
//overkill for this example, but custom matchers are quite easy with `gcustom`
HaveNilOwner := gcustom.MakeMatcher(func(object Object) (bool, err) {
return object.Owner == nil, nil
}).WithMessage("have nil owner)
func TestSomethingViaCustomMatcher(t *testing.T) {
g := NewGomegaWithT(t)
g.Expect(getObjects()).To(HaveEach(HaveNilOwner))
}
Note that in the gcustom
example above, Gomega does the type assertions for you and will guarantee that your matcher function always receives an object of type Object
(otherwise a clear error is emitted).
You can learn more about gcustom here and more about HaveField here. Finally, there are some patterns for building complex matchers via composition here - though the introduction of gcustom
does make a lot of this stuff much easier now.
Have you considered adding generics support to gomega? I work with nested structures a lot in tests, and reflection-based checks are verbose, and may not work for types not easily castable from interface. Something like: