Testing Go: Mocking Third Party Dependencies
In this article, we will explore how to write testable Go code. Often, we use external dependencies in our code and want to isolate those side effects during testing.
In our example code, we will connect to a database:
We want to test the Bar
function but not have to connect to a real database. In order to do so, we must mock the database.
Test stubs are programs that simulate the behaviors of software components (or modules) that a module undergoing tests depends on. https://en.wikipedia.org/wiki/Test_stub
To do so, we must write code that uses dependency injection. This means we will pass an interface to the Bar
function that handles interaction with the database.
Dependency injection is a technique whereby one object supplies the dependencies of another object. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. https://en.wikipedia.org/wiki/Dependency_injection
We will write an interface that handles any interactions with the third party library, use mockery to automatically generate mock implementations of the interface, then use testify
to make assertions on the mock object.
1. Interfaces
Interfaces are named collections of method signatures. Interfaces are a key part of Go.
Using an interface instead of directly using the third party code is a great practice because your code is less coupled to that specific dependency.
It is important to define an interface that matches your usage.We want to abstract the dependency away from the Bar function. The interface will declare which operations we need from the database:
In this case, we only need to insert data into a collection. This is a very basic example but you should get very creative with this process. You might only want to only accept a certain type:
Now that we’ve defined the interface, we need to create a default implementation that uses mgo. You could even create more than one implementation if you had multiple database drivers.
Finally, update your function. We will use the concept of dependency injection to provide the DataAccessLayer
interface to Bar()
instead of using mgo
directly.
2. Mockery
Mockery is a tool that automatically generates mock implementations of interfaces.
First, install mockery
go get github.com/vektra/mockery/.../
Then, generate mocks for the interface
$GOPATH/bin/mockery -name DatabaseAccessLayer
3. Testify
Testify is a testing library for making assertions and mocks. You can use it to make assertions on your mocks.
We can use testify to write a basic test using a mock DataAccessLayer. Please refer to the mock and assert documentation on how to use testify to test go code.
4. Wrapping up
Since we modified Bar()
to have a DataAccessLayer
, you will have to create a MongoDAL
to pass to Bar()
Conclusion
Interfaces are the key to writing testable Go code. They allow you to decouple your code from third party libraries, and make it clear which parts of the library your code requires. Mockery and Testify are important tools for Test Driven Development (TDD).