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
}
-
Event
is the struct that represents an event in the system -
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}
}
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 },
}
}
-
addEventFakeRepository
struct implements theAddInMemoryRepository
interface due to implementation of Add method so we can inject it into add event use case -
MockAddFn
it's the function used to mock the implementation ofAddInMemoryRepository
-
newAddEventFakeRepository
creates an instance ofAddEventFakeRepository
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)
}
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 !
📜 source code
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 !