Demystifying Go (Golang), Google’s Programming Language: Unit Tests - Native Mocks - Part 1
Vagner Nascimento
Software Engineer | Go (golang) | NodeJS (Javascrit) | AWS | Azure | CI/CD | Git | Devops | Terraform | IaC | Microservices | Solutions Architect
Introduction
Welcome to yet another chapter in our journey through unit testing in Go. In this phase, we will explore "root" strategies to mock external dependencies using only Go’s native packages. That’s right — no fuss, no third-party libraries, and no crazy hacks! If you, dear Gopher, are tired of frying your brain trying to test functions that rely on external connections (like databases or HTTP calls), take a deep breath and come with me. I’ll show you how to implement mocks, Go-native style!
Mock: What Is It?
First things first: a "mock" isn’t magic. It’s technology! A mock, also known as a stub or fake, is a way to replace the behavior of external components — for example, a database connection that refuses to cooperate or an HTTP request that takes 10 seconds to respond. Mocks are fundamental in unit testing because, as the name suggests, the unit being tested must be isolated. External dependencies only get in the way! The good news? In Go, we can handle this elegantly and without external libraries. Let’s explore some techniques.
High-Order Functions
Let’s start with the classic High-Order Functions. This technique involves passing the function to be mocked as a parameter. In the example below, the function OpenDb establishes a connection to the MySQL database using sql.Open. When we try to test it, the expected happens: the database fails, and the test becomes a lovely red mark on your screen.
To solve this, we apply High-Order Functions: we transform OpenDb to accept an opener function (SqlOpener). This gives us the ability to replace the original (real) behavior with our mock during testing.
Notice that we created the SqlOpener type, which matches the sql.Open signature, and adjusted the OpenDb function. Cool, right? But here’s the catch: this approach requires all calls to OpenDb to explicitly pass sql.Open. That could be a lot of work in a large project. If you’re not a fan of this technique, don’t worry! We have more tricks up our sleeve.
Monkey Patching
If High-Order Functions aren’t a good fit for your scenario, Monkey Patching might do the trick. Here, instead of changing function signatures, we create a public variable (e.g., SqlOpen) and set its default value to sql.Open. In our tests, we replace this variable with the mock function.
领英推荐
The good part about this technique? You don’t need to touch the original calls. The downside? Parallel tests won’t love you, as the shared variable modification can lead to inconsistencies. If creating a public variable isn’t ideal, you can create a private one (package scope) and write your tests in the same package. Simple, right?
If parallel testing isn’t a concern, Monkey Patching can be a solid solution!
Interface Substitution Interface Substitution
Is a powerful and idiomatic technique in Go. The trick here is to replace concrete types (like *os.File) with interfaces, which allows for mock implementations in tests. Here’s the problem: we have a function called ReadContents that reads files using os.File. When we test it with an empty file, the code explodes with a nil pointer. Why? Because you’re trying to do unit testing with real files — don’t do that!
So, how do we fix this? By analyzing the os.File type, we discover that it implements the ReadCloser interface, which has the Read and Close methods. Bingo! We can refactor ReadContents to accept an interface and then swap in a mock type during testing.
In the tests, we create a new type that implements Read and Close with the behavior we need. We then pass this mock directly to ReadContents — without modifying existing calls or contracts.
This approach is robust, reusable, and highly favored in the Go community!
Conclusion
As you’ve seen, Go is simple yet powerful when it comes to unit testing and native mocks. In this article, we explored three elegant techniques:
Each technique has its pros and cons, so choose the one that best fits your project and team. In the end, what matters most is that your tests are fast, isolated, and reliable.Enjoyed Part 1? Get ready because Part 2 will dive even deeper! Until then, keep testing, and may the mocks be with you!
Dev Leader Go | DDD | Clean Arch | EDA | OCI | AWS | GCP | Kubernetes | Serverless | Kafka | Typescript
2 个月Outstanding
Senior Fullstack Software Engineer | Senior Front-End Engineer | Senior Back-End Engineer | React | NextJs | Typescript | Angular | Go | AWS | DevOps
2 个月Insightful
Software Engineer | .NET | C# | Data Engineer | Azure | Power Platform | AWS | ERM | PySpark
2 个月Interesting
Fullstack Software Engineer | Backend Developer | Golang | Java | Microservices | SQL | NoSQL | React | Docker | AWS
2 个月Interesting...
Senior QA Automation Engineer | SDET | Java | Selenium | Rest Assured | Robot Framework | Cypress | Appium
2 个月Very helpful