Announcing New Gomega Matchers
Gomega is a Golang library for writing lovely matcher/assertion statements in your unit tests. For example:
Expect(color).To(Equal("red"))
This rich syntax also allows Gomega to display an informative error message when a test fails.
While Gomega has provided a wealth of primitive matchers such as BeTrue()
or HaveLen(...)
, it could use a little more power – a way to compose matchers into larger expressions.
After a little elbow grease, I’m happy to announce four new matchers are now available in the Gomega library: And
, Or
, Not
and WithTransform
.
Together, they provide a powerful way to compose existing matchers together into complex expressions.
Let’s take a brief look at the new matchers:
And
, also named SatisfyAll
, returns true only if all of the provided matchers return true.
Expect(number).To(SatisfyAll(
BeNumerically(">", 0),
BeNumerically("<", 10)))
Or
, also named SatisfyAny
, returns true only if at least one of the provided matchers return true.
Expect(number).To(SatisfyAny(
Equal(5),
Equal(10))
Not
negates the provided matcher, returning true only if the matcher returns false.
Expect(msg).To(Not(BeEmpty()))
// Yes, we could have just used `ToNot(...)` instead of `To(Not(...))`.
// But in nested expressions, `Not(...)` becomes an indispensable operator,
// as we'll see later.
WithTransform
lets you transform the value in some way before passing it to the given matcher.
getName := func(p Person) string { return p.FirstName }
Expect(person).To(WithTransform(getName, Equal("Roger")))
But we could also create a lightweight matcher – a simple function that composes existing matchers. That is a powerful use case enabled by WithTransform
and the other new matchers. So the above example would become:
// create a lightweight matcher
func HaveFirstName(n string) GomegaMatcher {
return WithTransform(func(p Person) string { return p.FirstName }, Equal(n))
}
// the matcher statement is simpler:
Expect(person).To(HaveFirstName("Roger"))
For more information refer to the Composing Matchers section of the Gomega documentation.
Concluding Example
I leave you with one slightly ridiculous example, but it demonstrates what’s possible if necessary.
Imagine you want to verify that a fetchPerson()
function eventually returns one of multiple acceptable Person values.
// Hypothetical Person struct
type Person struct {
FirstName string
LastName string
Age int
FetchedAt time.Time
}
When we compare two Person structs, we want to only consider certain fields. We don’t care about the FetchedAt
timestamp because that is somewhat random/unpredictable. So let’s create some lightweight field matchers:
// lightweight matchers for Person fields
func HaveFirstName(n string) types.GomegaMatcher {
return WithTransform(func(p Person) string { return p.FirstName }, Equal(n))
}
func HaveLastName(n string) types.GomegaMatcher {
return WithTransform(func(p Person) string { return p.LastName }, Equal(n))
}
func HaveAge(ageMatcher types.GomegaMatcher) types.GomegaMatcher {
// ageMatcher gives callers flexibility to use Equal, BeNumerically, etc.
return WithTransform(func(p Person) int { return p.Age }, ageMatcher)
}
We are now empowered to write complicated statements, such as this contrived example:
// fetchPerson() should eventually return either a "John Doe", or a non-Smith,
// or someone older than 65.
Eventually(fetchPerson).Should(
Or(
And(HaveFirstName("John"), HaveLastName("Doe")),
Not(HaveLastName("Smith")),
HaveAge(BeNumerically(">", 65))))
Hopefully you never have to write such convoluted tests, but it’s nice to know you could go there if you needed to.
Have fun testing your code with Gomega!