When Reality Gets in the Way: Why Do We Need Mocks and Fixtures in Go?
\n\nImagine you are testing a function that sends an email notification. Every test run sends a real email to the inbox. After ten tests, your inbox is full, and your colleagues curse the spam. Or worse: the function calls an external API that is paid or only works once a day.
\n\nIn the world of Go (or Golang, as it is often called), testing is not just a checkbox on a list. It is the foundation of reliability. But for tests to be fast and independent, we need to isolate the code from the outside world. This is where mock objects (mocks) and fixtures come into play.
\n\nIn this article, we will explore how to properly substitute dependencies (mock) and prepare data (fixtures) in Go. You will learn how to write tests that don't fail due to network issues, databases, or the time of day. Let's go!
\n\nWhat are Mock and Fixture in the Context of Go?
\n\nMock Objects: Doubles for Complex Systems
\n\nMock objects are software "stubs" that simulate the behavior of real components. Instead of calling a real database or HTTP service, we substitute an object that returns predefined responses.
\n\nIn Go, mocks are especially important because the language encourages working with interfaces. If your code depends on an interface, you can easily replace the implementation with a test one.
\n\nFixtures: Setting the Stage
\n\nFixtures are data or state that must be set up before a test begins. For example, creating a temporary database, populating it with test records, or configuring the file system.
\n\nUnlike mocks, which replace behavior, fixtures prepare the environment. They answer the question: "What needs to be ready for the test to start?"
\n\nWriting Mocks in Go: From Simple to Complex
\n\nThe Simplest Way: Manual Interface Implementation
\n\nGo does not require special libraries to create mocks. If you have an interface, you can write its test implementation manually. This is ideal for small projects.
\n\nLet's say we have an interface for sending notifications:
\n\npackage notifier\n\ntype Notifier interface {\n Send(to string, message string) error\n}\n\n// Real implementation (not for tests)\ntype EmailNotifier struct{}\n\nfunc (e *EmailNotifier) Send(to string, message string) error {\n // Sending a real email\n return nil\n}\n\nIn the test file, we create a mock:
\n\npackage notifier\n\nimport "testing"\n\ntype MockNotifier struct {\n Called bool\n LastTo string\n LastMsg string\n}\n\nfunc (m *MockNotifier) Send(to string, msg string) error {\n m.Called = true\n m.LastTo = to\n m.LastMsg = msg\n return nil\n}\n\nfunc TestSendAlert(t *testing.T) {\n mock := &MockNotifier{}\n // Use mock in a function that accepts Notifier\n SendAlert(mock, "user@test.com", "Hello!")\n \n if !mock.Called {\n t.Error("Send method was not called")\n }\n if mock.LastTo != "user@test.com" {\n t.Errorf("Incorrect recipient: %s", mock.LastTo)\n }\n}\n\nPros: full control, no dependencies.
\nCons: a lot of boilerplate code for complex interfaces.
Using testify/mock: Powerful and Convenient
\n\nThe testify library is the de facto standard for mocks in Go. It provides a flexible framework for creating mocks with argument and call count verification.
Installation:
\ngo get github.com/stretchr/testify\n\nExample using testify/mock:
\n\npackage user\n\nimport (\n "testing"\n "github.com/stretchr/testify/mock"\n)\n\n// Define the interface\ntype UserRepository interface {\n GetUser(id int) (string, error)\n}\n\n// Create the mock struct\ntype