GoMock generates mocks from Go interfaces so a test can specify expected calls, arguments, and results. Use it to check how a caller responds to a dependency without invoking that dependency’s implementation. This tutorial builds a small payment interface, generates its mock, and tests accepted, rejected, and failed charges.
An interface mock exercises the caller’s selected behavior without invoking the real implementation. It does not establish HTTP interoperability or provider correctness. This post shows the interface workflow and how to combine it with network-level mocks where useful.
What is GoMock and Why Choose It?
The maintained GoMock project is developed in Uber’s fork of the original golang/mock repository. It integrates with Go’s testing package and explicit code-generation workflows. Choose it when a test needs to verify interactions through a Go interface.
As you’ll see in the next section, GoMock works by generating a Mock Object from interfaces, meaning GoMock requires interfaces to function. In practical terms, generating a Mock Object is creating a new file that contains mock implementations of your interface and all the logic needed to create stubs and define mock behavior.
Choosing GoMock - or any mock package - depends on solving dependency issues in testing. While integration tests are valuable, as they test the entire chain of a request, they’re often time-consuming, complex, and/or costly. Additionally, if your mocks are properly configured and match the real dependency, you may find a decreased need to perform full integration tests in the first place.
Keep separate integration checks for the implementation behind the interface. A mock cannot show that the provider accepts the serialized request, authorizes the caller, or persists the intended state.

API Mocking Tools: Our Top 8 Picks
Compare API mocking tools and their test boundaries, including network mocks and function-level mocks.
Prerequisites and installation
Use a supported Go toolchain. This example pins GoMock and mockgen to v0.6.0, whose module requires Go 1.23 or newer. A module can live outside GOPATH; its module path determines local imports. See Go module layout.
Create a new directory and initialize the module:
mkdir gomock-getting-started
cd gomock-getting-started
go mod init example.com/gomock-getting-started
go get go.uber.org/mock@v0.6.0
The maintained project is Uber’s GoMock fork, using the import path go.uber.org/mock/gomock. Keep the generator and library versions aligned rather than letting different developer machines generate with unrelated versions.
Create a small dependency boundary
Save the following as payment.go. The example tests a caller’s minimum-charge rule and its interaction with a processor. It deliberately contains no real payment-provider implementation or credentials.
package payment
import "errors"
//go:generate go run go.uber.org/mock/mockgen@v0.6.0 -source=payment.go -destination=mock_payment_test.go -package=payment
type Processor interface {
Charge(amountCents int, token string) error
}
var ErrChargeTooLow = errors.New("charge too low")
type Client struct {
Processor Processor
}
func (c Client) Charge(amountCents int, token string) error {
if amountCents < 2000 {
return ErrChargeTooLow
}
return c.Processor.Charge(amountCents, token)
}
The interface describes the operation the caller needs. A real processor could implement it, but that adapter would need its own tests for HTTP encoding, authentication, status handling, and provider behavior. Integer cents keep this example focused on control flow; it is not a complete payment implementation.
Generate the mock
Run the directive in payment.go:
go generate ./...
The directive invokes mockgen’s source mode with an explicit destination and package. It creates mock_payment_test.go in the same package, where the test can use NewMockProcessor. Keep the generated file with the test sources when your team commits generated mocks. Do not edit its methods by hand; update the interface or generation settings and regenerate.
The Go generate documentation explains an important boundary: generation runs when explicitly requested. A normal build or test does not automatically run these directives. CI should either generate before testing or verify that committed generated files remain current.
Write tests with explicit expectations
Save this as payment_test.go:
package payment
import (
"errors"
"testing"
"go.uber.org/mock/gomock"
)
func TestChargeAccepted(t *testing.T) {
processor := NewMockProcessor(gomock.NewController(t))
processor.EXPECT().Charge(10000, "test-token").Return(nil).Times(1)
client := Client{Processor: processor}
if err := client.Charge(10000, "test-token"); err != nil {
t.Fatalf("Charge: %v", err)
}
}
func TestChargeRejectedBeforeProcessor(t *testing.T) {
processor := NewMockProcessor(gomock.NewController(t))
client := Client{Processor: processor}
if err := client.Charge(1000, "test-token"); !errors.Is(err, ErrChargeTooLow) {
t.Fatalf("got %v, want ErrChargeTooLow", err)
}
}
func TestChargeReturnsProcessorError(t *testing.T) {
processor := NewMockProcessor(gomock.NewController(t))
providerErr := errors.New("processor unavailable")
processor.EXPECT().Charge(10000, "test-token").Return(providerErr).Times(1)
client := Client{Processor: processor}
if err := client.Charge(10000, "test-token"); !errors.Is(err, providerErr) {
t.Fatalf("got %v, want processor error", err)
}
}
Run the tests:
go test ./...
The accepted path must call the processor exactly once with the specified arguments. The rejected path defines no expected processor call, so an accidental call fails the test. The error path checks that the caller preserves the processor error. Each test creates its own controller and mock, which keeps expectations from leaking between cases.
Passing *testing.T to gomock.NewController registers cleanup that checks outstanding expectations. In this workflow you do not need repeated defer Finish() calls. See the controller documentation for other reporter lifecycles.
Prove that the expectation can fail
Temporarily change the accepted test’s expected amount to 9999 while leaving the actual call at 10000, then run go test ./... again. The unexpected call should fail. Restore the amount afterward. This checks that the test’s failure path is active; it does not validate a real processor or its network client.
Matchers, actions, and call ordering
Use exact values for arguments that matter to the behavior under test. The matcher API includes Eq, Any, Not, and Cond; consult the versioned API before using a matcher name. Any can be useful for an unrelated context, but ignoring every argument can conceal a caller defect.
Use Return for a fixed response. Do runs an action but ignores that callback’s returned values; DoAndReturn uses them as the mocked method’s results. The callback must have the method’s signature. For example, within a test with processor already constructed:
processor.EXPECT().Charge(gomock.Any(), "test-token").DoAndReturn(
func(amountCents int, token string) error {
if amountCents < 2000 {
return ErrChargeTooLow
}
return nil
},
)
Mocks do not enforce call order by default. Use After or InOrder when the public behavior depends on sequencing, rather than making every test follow internal implementation order. These are alternative ways to express an ordering constraint, not a reason to declare the same expectations twice.
For clearer mismatch output, implement a custom matcher’s String method or use WantFormatter and GotFormatterAdapter. Changing a mock object’s String method is not the general mechanism for customizing expected-argument diagnostics.
Limitations of GoMock
As seen in this post, using GoMock isn’t too complex. But consider some of the limitations before integrating it into your infrastructure.
Only works with Go
You’re unlikely to have issues if your organization only uses Go. But many organizations are switching to microservices architecture that use different programming languages. In this case, or cases of uncertainties about the future use of Go, consider using a language-agnostic tool instead.
Sits directly in your codebase
A GoMock living as code directly in your codebase is positive and negative. It allows easy structure and access to writing tests.
But the inability to create a central system for tests is a possible drawback. For example, deciding whether to use a new version of GoMock organization-wide is time-consuming, depending on the number of individual projects.
Mostly suited for unit testing
GoMock replaces Go-interface calls. That boundary is often useful in unit or component tests, but it excludes the replaced implementation. Use httptest for a real local HTTP exchange and a disposable real dependency when its behavior is the question.
Only works with interfaces
If you’re using mockgen for code generation, then you’re limited to mocking interfaces. The upside is that it encourages the use of interfaces, which is arguably a good development paradigm. But it’s a limitation you should consider.
Conclusion
Use GoMock for selected interface interactions, httptest for HTTP handler and client boundaries, and real integration checks for behavior the doubles exclude. Recorded dependency responses can add representative fixtures, but still need reviewed matching rules and assertions.
Frequently asked questions
Which interfaces should I generate mocks for?
Mock a small interface at a dependency boundary the caller actually uses, such as a repository or payment adapter. Prefer the operations needed by that caller over copying a provider’s entire SDK. Use a simple fake when it expresses the scenario more clearly. Preserve separate tests for the real implementation’s serialization, authentication, and persistence behavior.
When should I regenerate mock code?
Regenerate when the interface, relevant imported types, generator version, or generation options change. Pin the generator version and review the resulting diff alongside the interface change. Keep generation explicit in the development or CI workflow: go test does not run go generate automatically. Changing internal implementation logic alone does not necessarily require a new mock.
When is an HTTP test server more useful than an interface mock?
Use httptest.NewServer when you need the real HTTP client to build a request and parse a controlled response, including paths, headers, encoding, and status handling. An interface mock skips that transport boundary. The local server still substitutes for the provider, so keep real-provider checks for authorization decisions, compatibility, and state the test handler does not implement.
Sources: Go interface design, Maintained GoMock and mockgen workflow, Explicit Go code generation, ResponseRecorder and local test servers, HTTP handlers and clients.