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
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
}
-
Eventis the struct that represents an event in the system -
EventSaveris 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}
}
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 },
}
}
-
addEventFakeRepositorystruct implements theAddInMemoryRepositoryinterface due to implementation of Add method so we can inject it into add event use case -
MockAddFnit's the function used to mock the implementation ofAddInMemoryRepository -
newAddEventFakeRepositorycreates an instance ofAddEventFakeRepositorythen 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)
}
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)
}
}
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 !

Top comments (4)
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 :-)
I think you are absolutely right, I will take a look at this tool soon, thanks !
Gostei do artigo! Clean Architecture é sempre a melhor opção.
Faz pouco tempo que estudo/aplico Clean Architecture em meus projetos, e já me tornei um grande fan.
Valeu Gustavo !