Go Development

Mocking With Gomock In Table-driven Tests

Discover how to run successful Golang testing and mocking with Gomock in table-driven tests. Learn more in the Gomock tutorial from GogoApps.

Arek Noster

Arek Noster, Golang Developer

Updated Feb 02, 2021

In this tutorial, we will present one the key way our Golang development team tackles the problem of writing precise, robust and explicit table-driven unit tests with the gomock package.

If you’re not familiar with the terms used above, we would recommend referring to the following texts first:

Continue reading this Gomock tutorial to discover the best practices when it comes to running a successful table-driven test with Gomock. 

Gomock Tutorial: Example Project

The best way of learning is by getting your hands dirty. To kick things off, let's set up a project to test. To show our approach with the gomock package, we’ll create a very simple "party" app. 

The main service in our app will fetch the names of people coming to the party and print out greeting messages for everyone to the console.

Note: we’ve placed all the source code on github so that you can play around with it. Throughout this article all the code you'll need is pasted here at each step. 

Define the domain

Firstly, you'll need to define a visitor:

type Visitor struct { Name string Surname string } func (v Visitor) String() string { return fmt.Sprintf("%s %s", v.Name, v.Surname) }

...and domain-level behaviors:

package party type Greeter interface { Hello(name string) string }
package party type VisitorGroup string const ( NiceVisitor VisitorGroup = "nice" NotNiceVisitor VisitorGroup = "not-nice" ) type VisitorLister interface { ListVisitors(who VisitorGroup) ([]Visitor, error) }

For this tutorial the implementation of the interfaces isn’t necessary. Throughout this Gomock tutorial won’t be using concrete implementation as we try to avoid running the dependencies in unit testing. 

Finally, to complete the set up, let's create the service with the method we would test:

package app import ( "fmt" "github.com/areknoster/table-driven-tests-gomock/pkg/party" ) type PartyService struct { visitorLister party.VisitorLister greeter party.Greeter } func NewPartyService(namesService party.VisitorLister, greeter party.Greeter) *PartyService { return &PartyService{ visitorLister: namesService, greeter: greeter, } } func (s *PartyService) GreetVisitors(justNice bool) error { visitors, err := s.visitorLister.ListVisitors(party.NiceVisitor) if err != nil { return fmt.Errorf("could get nice people names: %w", err) } if !justNice { notNice, err := s.visitorLister.ListVisitors(party.NotNiceVisitor) if err != nil { return fmt.Errorf("could not get not-nice people's names' ") } visitors = append(visitors, notNice...) } for _, visitor := range visitors { fmt.Println(s.greeter.Hello(visitor.String())) } return nil }

How to write tests with Gomock

You might have noticed that the GreetVisitors method is quite tricky to test. This is because:

  • it relies on its dependencies.

  • it prints its result and returns just an error (we cannot just check the return).

  • it has multiple points of return.

Since we’re unit testing, we’re supposed to exclude dependencies from this equation.

Gomock

There are many ways to mimic dependency's behavior in Golang. The most basic one being just explicitly writing the returns you need.

You can also save yourself some work and use a package for it. We chose gomock because it allows us to precisely set the functions' invocations and is actively maintained by a proactive community.

Handling the code generation

Mockgen is a go tool which generates structures that implement given input interfaces. It is installed the same way as other Golang tools. Provided you have everything set up all you need is to:

GO111MODULE=on go get github.com/golang/mock/[email protected]

or

go get github.com/golang/mock/mockgen

if your project happens to be in GOPATH.

Reflect and source mode

Mockgen has two modes. The definitions have been taken from the package repo:

  • reflect mode 'generates mock interfaces by building a program that uses reflection to understand interfaces.'

  • Source mode 'generates mock interfaces from a source file. It is enabled by using the -source flag.' source.

From our point of view the main differences are:

  • the source mode lets you generate unexported interfaces as it statically parses the code.

  • reflect mode can be used with go:generate annotations. To use these successfully we recommend referring to a great post by Sergey Grebenshchikov.

  • reflect mode gives you more control over what, where and how it is generated.

Both approaches are correct and will produce the right results in most cases.

For this tutorial we chose to use source mode. Using this approach, you sacrifice precision for a nice and defined structure, and also the possibility of mocking unexported interfaces.

Here is a Makefile which proves to be useful for that:

MOCKS_DESTINATION=mocks .PHONY: mocks # put the files with interfaces you'd like to mock in prerequisites # wildcards are allowed mocks: pkg/party/greeter.go pkg/party/visitor-lister.go @echo "Generating mocks..." @rm -rf $(MOCKS_DESTINATION) @for file in $^; do mockgen -source=$$file -destination=$(MOCKS_DESTINATION)/$$file; done

Using this Makefile rule, you'll get a mock folder which content directly reflects the structure of the project files:

Screenshot 2020-08-17 at 16.32.55.png

Resulting structure

As you need to add a new file's content to mocks, add it to mocks prerequisites in Makefile and then make mocks.

Using mocked objects

Now, let's get to testing. First, we'll start with a simple, single-case test (non-tabular approach):

func TestPartyService_GreetVisitors_NotNiceReturnsError(t *testing.T) { // initialize gomock controller ctrl := gomock.NewController(t) // if not all expectations set on the controller are fulfilled at the end of function, the test will fail! defer ctrl.Finish() // init structure which implements party.NamesLister interface mockedVisitorLister := mock_party.NewMockVisitorLister(ctrl) // mockedVisitorLister called once with party.NiceVisitor argument would return []string{"Peter"}, nil mockedVisitorLister.EXPECT().ListVisitors(party.NiceVisitor).Return([]party.Visitor{{"Peter", "TheSmart"}}, nil) // mockedVisitorLister called once with party.NotNiceVisitor argument would return nil and error mockedVisitorLister.EXPECT().ListVisitors(party.NotNiceVisitor).Return(nil, fmt.Errorf("dummyErr")) // mockedVisitorLister implements party.VisitorLister interface, so it can be assigned in PartyService sp := &PartyService{ visitorLister: mockedVisitorLister, } gotErr := sp.GreetVisitors(false) if gotErr == nil { t.Errorf("did not get an error") } }

Please refer to gomock documentation for more advanced expectation settings. Now, when the deferred ctrl.Finish() is called and the expected calls are not made, the tests will fail.

Table-driven tests

Now let's try to adapt gomock to use in table-driven tests.

We'll start with tabular test template generated by gotests tool:

func TestPartyService_GreetVisitors(t *testing.T) { type fields struct { visitorLister party.VisitorLister greeter party.Greeter } type args struct { justNice bool } tests := []struct { name string fields fields args args wantErr bool }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &PartyService{ visitorLister: tt.fields.visitorLister, greeter: tt.fields.greeter, } if err := s.GreetVisitors(tt.args.justNice); (err != nil) != tt.wantErr { t.Errorf("GreetVisitors() error = %v, wantErr %v", err, tt.wantErr) } }) } }

As you’ll most likely find, a few problems emerge.

The gomock's design doesn't allow you to both initialize and set up the expected function invocations in a single expression. For this reason, you need to do it "before" test cases.

Also, we want our tests to be idempotent, so each one would need different initialization. As you can imagine, with more than a few test cases it could get quite messy.

Luckily, we’ve managed to find a solution to this. Let's change the structure to the following. All changes are prepended with comments.

func TestPartyService_GreetVisitors(t *testing.T) { // we embed mocked objects instead of interface, so that we can set up expectations type fields struct { visitorLister *mock_party.MockVisitorLister greeter *mock_party.MockGreeter } type args struct { justNice bool } tests := []struct { name string // prepare lets us initialize our mocks within the `tests` slice. Oftentimes it also proves useful for other initialization prepare func(f *fields) args args wantErr bool }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() f := fields{ visitorLister: mock_party.NewMockVisitorLister(ctrl), greeter: mock_party.NewMockGreeter(ctrl), } if tt.prepare != nil { tt.prepare(&f) } s := &PartyService{ visitorLister: f.visitorLister, greeter: f.greeter, } if err := s.GreetVisitors(tt.args.justNice); (err != nil) != tt.wantErr { t.Errorf("GreetVisitors() error = %v, wantErr %v", err, tt.wantErr) } }) } }

Now our tests are fully idempotent thanks to gomock's controller being initialized inside t.Run and expectations being set for each individual case in prepare function.

Thanks to this feature, you are perfectly equipped to plan exactly what calls are made on your dependencies in each case. If they aren't - you'll get a message indicating which calls are not made.

At this stage it’s time to put everything together and fill in some test cases to test how it works!

tests := []struct { name string prepare func(f *fields) args args wantErr bool }{ { name: "visitorLister.ListVisitors(party.NiceVisitor) returns error, error expected", prepare: func(f *fields) { f.visitorLister.EXPECT().ListVisitors(party.NiceVisitor).Return(nil, fmt.Errorf("dummyErr")) }, args: args{justNice: true}, wantErr: true, }, { name: "visitorLister.ListVisitors(party.NotNiceVisitor) returns error, error expected", prepare: func(f *fields) { // if given calls do not happen in expected order, the test would fail! gomock.InOrder( f.visitorLister.EXPECT().ListVisitors(party.NiceVisitor).Return([]string{"Peter"}, nil), f.visitorLister.EXPECT().ListVisitors(party.NotNiceVisitor).Return(nil, fmt.Errorf("dummyErr")), ) }, args: args{justNice: false}, wantErr: true, }, { name: " name of nice person, 1 name of not-nice person. greeter should be called with a nice person first, then with not-nice person as an argument", prepare: func(f *fields) { nice := []string{"Peter"} notNice := []string{"Buka"} gomock.InOrder( f.visitorLister.EXPECT().ListVisitors(party.NiceVisitor).Return(nice, nil), f.visitorLister.EXPECT().ListVisitors(party.NotNiceVisitor).Return(notNice, nil), f.greeter.EXPECT().Hello(nice[0]), f.greeter.EXPECT().Hello(notNice[0]), ) }, args: args{justNice: false}, wantErr: false, }, }

While the function doesn't have enough coverage yet, this example is enough to demonstrate the idea.

Your thoughts on Gomock in Table-driven Tests

Throughout this tutorial we have presented the key ways to tackle the problem of writing precise, robust and explicit table-driven unit tests with the gomock package.

Now that you have a valuable approach  you can define test cases in a concise way without any extra work. 

Please share if the post helped you!

Get an estimate
Get an estimate
I accept the Cookie Policy