2019

We often need to simulate or mimic an object to create a deterministic, fast and network-independent object. Such an object is useful while testing. This practice is also known as mocking.

Let’s look at a few approaches to mock in Go.

Since database is one of the components which is often mocked, let’s look at a stubbed out example for it.

Example

So if you have a type User.

...
type User struct {
    ID string
    Name string
}

and a type Storage which represents a database.

...
type Storage struct {
    db *sql.DB
}

func (s *Storage) CreateUser(user User) error {
    ...
    s.db.Exec(...)
    ...
    return nil
}

In the above psuedo-code there are two ways to mock the behaviour of Storage without using a real db connection.

The first way is to mock the db object itself. Driver-level mocking is not trivial but it’s possible to find packages out there: DATA-DOG/go-sqlmock. We won’t talk about it in this post.

The second way is to mock the CreateUser method. Let’s see the approaches to mock out the methods.

How to mock

1. Using Interfaces

To mock out the Storage type, we can declare an interface to have a real and a mock implementation.

So instead of creating a type Storage struct, we create a type Storage interface.

...
type Storage interface {
    CreateUser(user User) error
}

Implement a real storage for the interface Storage.

...
func NewStorage(db *sql.DB) Storage {
    return &defaultStorage{db : db}
}

type defaultStorage struct {
    db *sql.DB
}

func (d *defaultStorage) CreateUser(user User) error {
    ...
    d.db.Exec(...)
    ...
    return nil
}

and a mock storage.

...
func NewMockStorage() Storage {
    return &mockStorage{users: make(map[int64]User)}
}

type mockStorage struct {
    users map[int64]User
    lastID int64
}

func (m *mockStorage) CreateUser(user User) error {
    ...
    m.users[lastID] = user
    ...
    return nil
}

Alternatively, one could split this into multiple packages to keep the imports more sensible.



pkg/storage/
    mocksql/
    sql/
    storage.go

sql.go

...
func New(db *sql.DB) Storage {
    return &storage{db: db}
}

type storage struct{
    db *sql.DB
}

func (s *storage) CreateUser(user User) error {
    ...
    s.db.Exec(...)
    ...
    return nil
}

mocksql/sql.go

...
func New() Storage {
    return &mockStorage{users: make(map[int64]User)}
}

type mockStorage struct {
    users map[int64]User
    lastID int64
}

func (m *mockStorage) CreateUser(user User) error {
    ...
    m.users[lastID] = user
    ...
    return nil
}

storage.go

...
type Storage interface {
    CreateUser(user User) error
}

func NewSQL(db *sql.DB) Storage {
    return sql.New(db)
}

func NewMockSQL() Storage {
    return mocksql.New()
}

To import.

user.go

import "github.com/myuser/mypkg/pkg/storage"

...

storage := storage.NewSQL(db)

user_test.go

import "github.com/myuser/mypkg/pkg/storage"

...

storage := storage.NewMockSQL()

One could imagine that if the Storage interface has tens of methods or there are several interfaces like it, it could get quite cumbersome to write out the mock implementations for it. Fortunately there’s tooling to help out.

Generating Mocks

1. Use an editor plugin

In vscode open the command paletter(cmd+shift+p), move cursor on the target stub and run Go: Generate Interface Stubs. Most of the editors supporting Go have this feature integrated.

2. Use a cli or package

testify/mock
golang/mock

2. Using Functions

While using interfaces to mock out behaviour is quite alright, it might look too permanent for some projects. Also, one could rather want an approach where the mocking code is completely contained within a test function. There could be two approaches here.

a. Custom function types

This approach is based on making CreateUser as a field of the struct instead of a method on the struct. Go treats functions as a first class citizen.
store.go

...
import "github.com/adnaan/mypkg/sqldb"

type Storage struct {
    CreateUser func(user User) error
}

...
storage := Storage {
    CreateUser: func(user User) error {
                ...
                db := sqldb.Get()
                db.Exec(...)
                ...
        return nil
    },
}

storage.CreateUser(user)

store_test.go

...

var lastID = 0
var users = make(map[int64]User)

mockStorage := Storage {
    CreateUser: func(user User) error {
                ...
                users[lastID] = user
                ...
        return nil
    },
}

mockStorage.CreateUser(user)

b. Mocked struct with custom function types

In this approach one still has a Storage interface but also implements a mock struct which holds mocked equivalents of the interface methods.

store.go

...
type Storage interface {
    CreateUser(user User) error
}

func New(db *sql.DB) Storage {
    return &storage{db: db}
}

type storage struct {
    db *sql.DB
}

func (s *storage) CreateUser(user User) error {
    ...
    s.db.Exec(...)
    ...
    return nil
}

store_test.go

...

type StorageMock struct {
    CreateUserFunc func(user User) error
}

func (s *StorageMock) CreateUser(user User) error {
    return mock.CreateUserFunc(user)
}

...
var db = make(map[int]User)
var lastID = 0

...

mockStorage := &StorageMock{
    CreateUserFunc: func(user User) error {
        ...
        db[lastID] = user
        ...
        return nil
    }
}

err := mockStorage.CreateUser(user)

There is tooling available to generate StorageMock via moq

3. Using Embedded Types

We might want to partially mock a type. The technique of embedding types allows us to mock out only the methods we want to.

store.go

...
type Storage interface {
    CreateUser(user User) error
    ExistsUser(userID int64) bool
}

func New(db *sql.DB) Storage {
    return &storage{db: db}
}

type storage struct {
    db *sql.DB
}

func (s *storage) CreateUser(user User) error {
    ...
    s.db.Exec(...)
    ...
    return nil
}

func (s *storage) ExistsUser(userID int64) bool {
    ...
    s.db.Exec(...)
    ...
    return false
}

store_test.go

...

type storageMock struct {
    storage // embed type storage which implements the Storage 
            //interface
    users map[int64]User
    lastID int64
}
// Mock only the CreateUser method
func (s *storageMock) CreateUser(user User) error {
        ...
        s.users[lastID] = user
        ...
        return nil
}

...

mockStorage := &storageMock{}
// calls the mocked CreateUser Method
err := mockStorage.CreateUser(user)
// calls the original ExistsUser Method
err = mockStorage.ExistsUser(111)

Conclusion

Depending on a project’s complexity, scope and use cases either one of the above mocking approaches could fit. One of the considerations could be other behaviours(apart from mocking) needed for a type. For e.g. the Storage type could have multiple database backends implementations like inmem, postgres, mysql etc.

Mock packages in golanglibs.com.

Feedback is welcome on Twitter too.

Mock well and prosper 🖖