Skip to Content

Stubbing Dependencies in Go

A quick guide to stubbing dependencies in Go

Posted on 4 mins read

Very often in our applications we use third party dependencies to access data. This could be either a database connection, a third party API client or a caching layer. Subsequently, in unit tests, these dependencies need to be stubbed in order to avoid expensive remote calls as well as to have a deterministic test suite.

Go’s interface provides a very elegant way of stubbing these dependencies. With interface, we can substitute the dependencies with stubs when testing, allowing us to control the behaviour of the dependencies.

Stubbing a Redis client

In this example, we’ll look at how we can stub a Redis client, so that we can test our code without having to depend on a real Redis host. We will be working with two of Redis commands: HGETALL and HMSET

The Go client function signatures of these two commands are:

To get the actual result of the commands, we would be using the Result() function implemented by both StringStringMapCmd and StatusCmd, which returns a value and error pair.

In order to create a stub of Redis for our tests, we need to define a proxy using Go’s interface. This interface has 2 signatures which wrap the Redis commands we are interested in.

In this interface, we no longer return either a StringStringMapCmd or StatusCmd, but the result of the command and error if there is any. This interface can now be implemented by either a concrete proxy using a real Redis client or a stub with fixed return values.

The actual Redis client is now wrapped in this proxy implementation, with the relevant wrapper methods. In tests, we could use a stub Redis with a preset data.

Next, we are going to simulate writing a sample user account repository which fetches a user account from Redis based on a given id. We would have a constructor function that injects a Redis proxy as dependency into the repository, then returns a pointer to the repository. The repository would have 2 methods: FetchAccount() and UpdateAccount(). The former fetches an account by its id from the repository and the former updates an existing account with new data.

Following a Test-Driven Development approach, we start with a test for the repository, in a file account_repository_test.go. We first set up a stub Redis proxy which implements the RedisProxy interface above, along with some fake data.

The first test case checks that FetchAccount() returns the correct user account with its user data. We start with creating a repository with the fake data above. In the function createRepositoryWithData(), we inject the stub Redis proxy we created into the repository. The test would then run against the stub Redis proxy. We then assert that the account details are correctly retrieved.

Let’s now implement this method. We start by creating a proxy for Redis, which wraps around the HGetAll() and HMSet() methods. We’ll place this in a file redis.go.

Next, we create a constructor function for AccountRepository in account_repository.go, which takes a RedisProxy implementation and returns a pointer to an AccountRepository with a RedisProxy.

The rest of the implementation is simply calling the HGetAll() method on the RedisProxy, and mapping it into an Account model.

Now that we are done with the first method, let’s write the second test case to check that UpdateAccount() returns the updated user account.

Again, in the implementation, we simply call HMSet() on the RedisProxy, and return the account fetched from Redis, which now would have been updated with the new data.

Parting notes

I did not go into too much detail in testing edge cases and other scenarios, to keep the focus on the stubbing technique. With this basic set up, it is easy to extend the proxy interface to cover other methods of Redis.client. Similar technique could also be used to stub other dependencies such as an api client or a database. The full code can be found on GitHub. This post is partly inspired by this guide on kafka and go, which inspired me to test the code without relying on a real Redis host.