Gomega Matcher : A guide to developing your custom tests in Golang

Dipto Chakrabarty
4 min readMar 19, 2022

Lets be honest when testing on golang comes to our mind we think of gomega and gingko.

In fact majority of the tests that developers write for their golang application rely on gomega and gingko , however as applications become more complex it we find ourselves in a situation where we have to extend the functionality of our tests.

This is where we can develop gomega matchers which are specialized methods that can be utilized to test unique functions.

This comes after I was working on a pull request for the kubernetes community where I had to implement one such function myself which you can view here (custom gomega matcher to test tables) .

All the code for below can be found here.

About Gomega Matchers

As per documentation as found here we can develop custom gomega matchers which can extend the power of our testing

type GomegaMatcher interface {
Match(actual interface{}) (success bool, err error)
FailureMessage(actual interface{}) (message string)
NegatedFailureMessage(actual interface{}) (message string)
}

A custom Gomega matcher requires three methods , the main implementation is written in the Match method. Match takes in one parameter — actual which is a generic interface. If the actual and expected values are same then Match will return true whereas if they are not it returns false.

If the GomegaMatcher uses the Should case and match returned false it uses the FailureMessage method to return the error.

If the GomegaMatcher uses the ShouldNot case and match returned true it uses the NegatedFailureMessage error.

Code Example

Comparison

Let us first define a struct which will hold our values

// Person Struct to Compare
type Person struct {
Age []int
}

This is a person struct , we are going to add age values to this and check if age follows the following equation with the expected values

y (expected) = 2*x (age) + 10

We define a match method which will perform the comparison in this case

// Match expects the actual item which is compared to the
// target returned from the custom Gomega function
func (p *Person) Match(actual interface{}) (bool, error) {
switch actual := actual.(type) {
case Person:
for i, j := range actual.Age {
if j != (2*p.Age[i] + 10) {
return false, fmt.Errorf("Wrong Person")
}
}
}
return true, nil
}

Here since in the input we are taking in an interface we asset its type , Age is a map type so we iterate over it and compare . Actual interface is the original value that we want.

If all conditions are satisfied we return no error and true or else a false and error message.

We can define the FailureMessage method and NegatedFailureMessage method too.

// FailureMessage method for Person struct
func (p *Person) FailureMessage(actual interface{}) string {
return fmt.Sprintf("Expected age to be %v but received %v", actual, p.Age)
}
// NegatedFailureMessage method for Person struct
func (p *Person) NegatedFailureMessage(actual interface{}) string {
return fmt.Sprintf("Expected age to be %v but received %v", actual, p.Age)
}

Defining our structs

Create a function which perform the actual tests , the tests will have the format of structs and processing will occur on them.

package testimport (
"testing"
"goTest/human". "github.com/onsi/gomega"
"github.com/onsi/gomega/types"
)
func TestHumanStruct(t *testing.T) {
tests := []struct {
name string
targetAge []int
humanTarget human.Person
want bool
}{
{
name: "Compare Age Correct",
targetAge: []int{10, 20},
humanTarget: human.Person{Age: []int{30, 50}},
want: true,
},
{
name: "Compare Age Incorrect",
targetAge: []int{11, 25},
humanTarget: human.Person{Age: []int{32, 60}},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
g.Expect(tt.humanTarget).Should(MatchAge(tt.targetAge))
})
}
}

The tests are defined as structs and the there are fields present in them

  • name -> define name of test
  • targetAge -> the age we want to test
  • humanTarget -> Actual values present here
  • want -> boolean result expected

The targetAge field has to be converted to the required struct type which will then be used in the tests. We have the MatchAge function for that

// MatchAge is the Custom Gomega Testing Function
func MatchAge(a []int) types.GomegaMatcher {
return &human.Person{Age: a}
}

MatchAge will create an instance of the human object , we set its return type as GomegaMatcher. This method will invoke the rest of the methods like Match , FailureMessage of the custom gomega matcher.

The actual values (in this case tt.humanTarget) will be passed onto the match function as an interface argument.

Once that is then the computation will be performed within the match function and the results will be returned

Running the Tests

Use the command to run the test

go test -v

For comparing tables you can refer to the following here -> Tables Custom Gomega Matcher

Custom gomega matcher for tables such as “github.com/olekukonko/tablewriter” can be developed as such to compare table output with testcases.

This way powerful custom gomega matchers can be developed , these matchers are used in multiple open source projects including kubernetes and its subprojects.

--

--

Dipto Chakrabarty

Site Reliability Engineer , I talk about Devops Backend and AI. Tech Doctor making sure to diagnose and make your apps run smoothly in production.