Recently I started using Golang for my web projects. One of the challenges that I faced with Golang was mocking client calls in Unit tests.
I was using Firestore for a database and in my unit tests, I wanted to mock firestore client. Now, in Golang I always avoid using external libraries so I needed an easy solution that could be used to mock client objects.
Interface is always a great solution for most of the problems in Golang. Golang Interfaces are similar to interfaces in OOP, in OOP, you have to use implement keyword a lot while implementing an interface. But in Go, you do not explicitly mention if a type implements an interface.
firestoreClient.go
//// Here we have defined the interface for firestore client
type FirestoreClientInterface interface {
Get(id string) (map[string]interface{}, error)
}
//// This is a struct implementing FirestoreClientInterface as
//// it has a method signature similar to the interface,
//// we have defined above.
//// We don't need to explicitly mention that this struct implements FirestoreClientInterface.
//// Golang resolves this itself.
type FirestoreClient struct{}
//// This method will have no logic or conversion in it
//// it will just make client call and return the data.
func (firestoreClient *FirestoreClient) Get(id string) (map[string]interface{}, error) {
}
Now, we can have another DAO layer where we use this interface for database calls.
productDataDao.go
type productDataDao struct {
Client FirestoreClientInterface
}
// Returns productDataDao object with default client initialized.
func NewProductDataDao() *productDataDao {
return &productDataDao{Client: &FirestoreClient{}}
}
func GetProductDataDao(client FirestoreClientInterface) *productDataDao {
return &productDataDao{Client: client}
}
func (dao *productDataDao) Get(id string) (string, error) {
}
Here comes the main part, where we have to write unit tests for this.
As Golang supports first-class functions, we can just create a mock object and make it return function instead of any data.
productDataDao_test.go
//// This struct implements the client interface,
//// that we have defined above (same method signature)
type mockFireStoreClient struct {
}
var (
fireStoreMockCall func(id string) (map[string]interface{}, error)
)
//// This call just returns a function -> fireStoreMockCall()
//// We can change fireStoreMockCall to change
//// behavior of this call.
func (mockFireStoreClient *mockFireStoreClient) Get(id string) (map[string]interface{}, error) {
return fireStoreMockCall(id)
}
When we are writing test and need to mock this client, we can just re-assign fireStoreMockCall and it will change its behavior.
productDataDao_test.go
func TestGet(t *testing.T) {
fireStoreMockCall = func(id string) (map[string]interface{}, error) {
return "Unit tests", nil
}
productDao := GetProductDataDao(&mockFireStoreClient{})
actual, err := productDao.Get("testId")
if err != nil {
t.Errorf(constants.UnexpectedError)
}
if actual != "test string" {
t.Errorf(constants.UnexpectedError)
}
}
If we have multiple client calls to mock, these mocks can be moved to different package, so that it does not clutter the test code.
Do you find this strategy helpful? What are some of your ways to mock in Golang? How have they worked for you? Comment for discussion ๐
Happy Coding ๐ค