This lesson is locked. Login or Subscribe for more access!

Mocking with goMock

Duration: 51 mins

goMock is a widely used mocking framework that can help make your tests more complete. Let's see how

Instructor

Andrea Medda

Share with a friend!

Transcript

Hello everyone and thank you for joining in. My name is Andrea. I’m really happy that you’re taking your time to learn a bit more about mocking in Go using Mockgen and GoMock, and what the hell is all of this, right?

So first of all, what’s the objective of mocking? Mocking is very useful when doing unit tests and it allows you to really isolate specific behaviors to be tested in isolation, which, in simple words, means there’s a bunch of very complex code that I want to test, making sure that my application behaves well with it.

For example, calling some external API, but actually my test would be quite cumbersome to spin up that environment to reach the real API, so I just want to make sure that when that API responds in a specific way, I can return a specific behavior in my application.

This is often done with mocking, and it’s quite popular in object-oriented programming, but this is also a very powerful technique in other programming languages, including in Go.

The usual issue with mocking, in general, is that it can become quite tedious to maintain, especially if you’re familiar with maintaining mocks by hand or using other libraries such as the Testify mock package. You have to make code changes, and we all know that code is liability.

After all, we need to maintain that code, and sometimes there are better ways to deal with this, like spending more time actually writing tests rather than writing mocks and being really in charge of making all types of assertions and expectations that you want in specific types of mocks.

This is where GoMock comes into play. GoMock is a library for Go that was mainly maintained by Google. Unfortunately, it’s been deprecated a few years ago, but the good news is that it was forked by Uber.

Uber is big on Go, and they decided to fork it and started to maintain it. GoMock is a library that allows us to generate mocks starting from some kind of specification, usually defined by interfaces.

So given an interface, I can expect my mock to implement that interface, and using dependency injection principles, I can basically substitute some piece of code that is really satisfied by that cumbersome piece of logic to call, for example, an external specification client with my mock.

Then I can make that expectation and test different test cases that I couldn’t test before. GoMock is quite nice to work with. I’ve been working with GoMock for about five to six years. I really don’t have anything bad to say about it.

Of course, you need to learn the ins and outs of it, but overall, it’s a great tool in comparison to other libraries that I’ve used in the past. So in this lesson, we’re going to have a look at GoMock.

We’re going to see how to install GoMock in different ways—using Go tools or installing it globally. We’ll talk about GoGenerate and how this plays well with Mockgen.

We’ll talk about how the mocks are generated and how they behave. We’ll try to write very simple test cases that use GoMock and do basic assertions.

Then we can explore some more complex things—for example, let’s make sure the mock is being called two or three times, and let’s make sure different assertions and expectations are called in a specific order. Also, we’ll look into unexpected types, if that’s relevant.

Let’s jump in, why not? Okay, let’s get serious. Let’s look into installing GoMock. Before we do that, let’s check my Go version, so you can see what I’m running.

It’s the latest Go version, 1.24, on Mac. It doesn’t make a big difference. We can look into how to install GoMock locally on our machines. This is done with go install.

Here you can see go install gomock, and now if you run Mockgen simply, we should see some type of output. This is returning an error actually. There’s a --flag, so we can check it with --version, which says 0.5.0.

But there’s a better way to do this, and we can follow the latest guidance from the Go website for Go 1.24 using the tool directive. There’s a way to install these applications as local tools for our own project, which is way better because we also make sure the same tool runs on the same version on my machine or on your machine if we’re working in the same repository.

This leads to way less noise when doing code reviews and PRs, because often if you run these tools—not just GoMock, even others—on different versions, you might get different outputs even though semantically we’re really doing the same thing. You really don’t want that back and forth.

So you can install the tool using -tool for tooling and install gomock locally. If we go here and run go mod tidy and then go mod vendor, we can store the vendors locally.

Maybe my GoLand doesn’t understand this directive, but it’s all good. We are installing the GoMock library as 1.6.0, and now we can try to use GoMock as a local tool.

Of course, we still don’t have anything to generate, but we can try to get the version, which is 1.6.0, as mentioned. That’s nice. I suggest going down this route rather than having it global.

Of course, you can install it globally if you just want to play around, but when you’re working on a production-ready repository and collaborating with people, it’s much better to keep it local.

Okay, on to the next step. Let’s try to find a simple way to demonstrate how the tool works, right? Let’s assume we’re building some type of system that allows people to order new gopher plushies from the store, which can have different colors—like only blue or pink—and we store the information about the gophers (colors, quantity, prices, etc.) in a database.

Let’s assume it’s Postgres, but it doesn’t matter too much. Usually, to test that, you’d do an integration test by spinning up the real database.

Let’s assume that’s complicated, though, and we just want to test some narrower scenarios. That’s where mocking can help, so we can isolate small behaviors—like if the database replies “records not found,” how does my code behave?

Why not do that? I can create a new package called gopher, and we’ll have some logic. That logic lives in a file called store.go, which abstracts the interaction with the database.

Then we’ll have the actual implementation in postgres.go or something. Let’s define a Storer interface that includes methods like Store and Retrieve.

We might define a Gopher struct that has Name, Color, and maybe Price. The Store method in that interface takes a context and a Gopher, returning an error. The Retrieve method takes a context and a gopher name, returning a Gopher plus an error.

Then, in the real code, we have some Postgres struct that implements Storer, so if we inject Postgres into our code, it’ll compile and just work. Then we can do a constructor or something to set it up.

But in the main function, you might do postgres.NewStore to build your Postgres store, and then pass that into gopher.NewStore to create the higher-level logic. Then you can do stuff like gs.Store(ctx, someGopher) and so on.

But let’s say we want to test it. We can do an integration test, but maybe that’s cumbersome. So we’ll use GoMock.

If I define the interface in store.go, I can generate the mock by running Mockgen and pointing it at that file, telling it to generate into mock_store_test.go for the Storer interface.

Then in my test code, I create a gomock.Controller, a mockStorer, and inject it into my Gopher store. So if I want to test that the store method handles an error properly, I do: mockStorer.EXPECT().Store(ctx, gopher).Return(errors.New("some DB error")).

Then I call gs.Store(ctx, gopher). If I get no error, or the wrong one, my test fails. Otherwise, if it’s the correct error, the test passes. That’s how GoMock helps.

If I want to test success, I just have that call return nil. If I want to test a conflict error or a not-found error or a retry scenario, I can do that too.

I can chain calls or specify Times() or InOrder, all of which GoMock supports. If my code has logic like “try three times, then fail,” I can have the first two calls return some “retryable” error, and the last call return success, so I confirm that the logic does indeed keep trying.

This is a big advantage over a real environment test if it’s tricky to simulate those conditions. Also, GoMock argument matching can be very strict or we can say gomock.Any() for arguments if we don’t care exactly what they are.

That’s helpful in certain scenarios, like contexts that might include certain metadata or vary slightly from test to test.

We also talked about how to handle installing GoMock as a local tool, or using GoGenerate to run the Mockgen command automatically whenever we change an interface.

That’s great because we don’t have to remember the command or rely on a global version. We can do a tools.go file with something like: // go:generate go run github.com/golang/mock/mockgen -destination=./gopher/mock_store_test.go -package=gopher_test github.com/you/yourrepo/gopher Storer.

Then we do go generate ./... and everything regenerates. Some teams commit mocks; some generate them on the fly. It’s up to you.

Either way, we pin the GoMock version so we don’t get random diffs. All of this is basically the workflow for how we can mock dependencies in Go using GoMock.

We covered what mocking is, how it helps isolate tests, how to install and generate mocks, how to use them in your tests, and how to automate the generation process.

I hope it helps you see how to avoid the complexity of writing mocks by hand. Give GoMock a try; it’s a really nice tool.

I’ve been using it for many years, and it’s pretty standard at places that rely on Go heavily, like Uber. See you in the next tutorial, and take care.