DEV Community

Cover image for How I mock unit tests in Golang
Christian Seki
Christian Seki

Posted on • Edited on

How I mock unit tests in Golang

First of all I just want you guys to know that I'm not a golang expert, I started to use golang as a hobbyist a few months ago but now every web backend project I use it.

System

Alt Text

Note the arrow direction what means that Domain layer have no Idea about App layer so I promise I'll try to keep this in the code.

That's all ! Lets start as simple as possible to focus on tests it self and maybe further I can add more layers into this system and update this diagram but thats it for now.

Domain Layer

event.go

package domain

import "time"

type Event struct {
    Title           string    
    Date            time.Time 
    Place           string        
    Category        string    
    KeyWords        []string  
}

type EventSaver interface {
    Save(Event) error
}
Enter fullscreen mode Exit fullscreen mode
  1. Event is the struct that represents an event in the system
  2. EventSaver is the interface that will be implemented by someone who wants to save an event somehow, often called as an use case of the system.

Use Cases Layer

For simplicity, I made just one use case named AddEvent Memory that try to save the event and pass the error to who called it.
event.go

package usecases

import (
    "github.com/iamseki/dev-to/domain"
)

type AddInMemoryRepository interface {
    Add(domain.Event) error
}

type AddEventInMemory struct {
    repository AddInMemoryRepository
}

func (usecase *AddEventInMemory) Save(e domain.Event) error {
    err := usecase.repository.Add(e)
    return err
}

func NewAddEventInMemory(r AddInMemoryRepository) *AddEventInMemory {
    return &AddEventInMemory{repository: r}
}

Enter fullscreen mode Exit fullscreen mode

Testing

With the testing built in package we can easily start to write unit tests in go, so lets do it !

Lets focus on this peace of code:

type addEventFakeRepository struct {
    MockAddFn func(domain.Event) error
}

func (fake *addEventFakeRepository) Add(e domain.Event) error {
    return fake.MockAddFn(e)
}

func newAddEventFakeRepository() *addEventFakeRepository {
    return &addEventFakeRepository{
        MockAddFn: func(e domain.Event) error { return nil },
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. addEventFakeRepository struct implements the AddInMemoryRepository interface due to implementation of Add method so we can inject it into add event use case
  2. MockAddFn it's the function used to mock the implementation of AddInMemoryRepository
  3. newAddEventFakeRepository creates an instance of AddEventFakeRepository then mocking the case of success returning error as a nil.

However if the case of success its not desirable, we can set the mock function as follow:

func TestAddEventInMemoryCustom(t *testing.T) {
    r := newAddEventFakeRepository()
    r.MockAddFn = func(e domain.Event) error { 
        // do something diferent here !
    }
    sut := usecases.NewAddEventInMemory(r)
}
Enter fullscreen mode Exit fullscreen mode

Source code of all test in usecases package:
event_test.go

package usecases_test

import (
    "testing"

    "github.com/iamseki/dev-to/domain"
    "github.com/iamseki/dev-to/usecases"
)

type addEventFakeRepository struct {
    MockAddFn func(domain.Event) error
}

func (fake *addEventFakeRepository) Add(e domain.Event) error {
    return fake.MockAddFn(e)
}

func newAddEventFakeRepository() *addEventFakeRepository {
    return &addEventFakeRepository{
        MockAddFn: func(e domain.Event) error { return nil },
    }
}

func TestAddEventInMemorySucceed(t *testing.T) {
    r := newAddEventFakeRepository()
    sut := usecases.NewAddEventInMemory(r)

    err := sut.Save(domain.Event{})
    if err != nil {
        t.Error("Expect error to be nil but got:", err)
    }
}
Enter fullscreen mode Exit fullscreen mode

Considerations

I hope this can be useful for someone, I really enjoy to mock my tests in the way I describe here but I'm not pretty shure about how idiomatic it is so I open to suggestions !

📜 source code

Top comments (4)

Collapse
 
jumanjii profile image
Allan Jacquet-Cretides

Nice article!

Just sharing but at some point with a codebase that became bigger and bigger, a really nice tools to automatically generate mock for your interfaces is github.com/vektra/mockery.

You should try to play with it :-)

Collapse
 
chseki profile image
Christian Seki

I think you are absolutely right, I will take a look at this tool soon, thanks !

Collapse
 
gusandrioli profile image
Gustavo Andrioli

Gostei do artigo! Clean Architecture é sempre a melhor opção.

Collapse
 
chseki profile image
Christian Seki

Faz pouco tempo que estudo/aplico Clean Architecture em meus projetos, e já me tornei um grande fan.

Valeu Gustavo !