I think there are at least two alternatives.
Use always a function field
When the function mocked is something not related to the struct I'm mocking, maybe a third party function, or something written from another component of my application.
I will assign the real working one when initializing the service or the mocked one on the tests.
// service.go
type MyService struct {
getRandomID func() string
}
type Car struct {
ID string
Name string
}
func (s *MyService) NewCar() (*Car, error) {
car := Car{
ID: s.getRandomID(),
Name: "ThisCar",
}
return &car, nil
}
// service_test.go
func newIDsForTests() func() string {
i := 0
return func() string {
i++
return fmt.Sprintf("%024d", i)
}
}
func TestNewCar(t *testing.T) {
s := MyService{
getRandomID: newIDsForTests(),
}
actual, err := s.NewCar()
if err != nil {
panic(err)
}
expected := Car{ID: "000000000000000000000001", Name: "ThisCar"}
if *actual != expected {
panic("cars don't match")
}
}
The Go Playground working example
Use a function field only when mocking
When the function to be mocked is something really related to the struct I'm working with, that is part of this component.
I will always use the real working, and assign a mock function when needed for the tests.
While I think this solution is quite ugly, I also think that is for sure easily to use and to maintain, while also let you unit test your code at 100%!
My idea is to add a field mockedGetSecond
to the struct, and to set its value only in the tests where you want to mock the real getSecond
. In the real implementation you have to add a check of course, that if this func isn't nil
, it must be used.
This is probably not a good pattern, or something I would like to use often, but I think I'll will use it to mock a function that do a lot of logic (and a lot of db calls, and need various input, ...) and is often called in the functions in the same service.
// service.go
import (
"fmt"
"testing"
)
type MyService struct {
mockedGetSecond func() (string, error)
}
func (s *MyService) GetFirst() error {
secondVal, err := s.getSecond()
if err != nil {
return err
}
fmt.Println("getSecond returned: ", secondVal)
return nil
}
func (s *MyService) getSecond() (string, error) {
if s.mockedGetSecond != nil {
return s.mockedGetSecond()
}
// very complex function
return "real", nil
}
// service_test.go
func TestGetFirst(t *testing.T) {
myService := MyService{
mockedGetSecond: func() (string, error) {
return "mocked", nil
},
}
err := myService.GetFirst()
if err != nil {
panic(err)
}
}
func TestGetSecond(t *testing.T) {
myService := MyService{}
actual, err := myService.getSecond()
if err != nil {
panic(err)
}
if actual != "real" {
panic("I would expect 'real'")
}
}
The Go Playground working example
=== RUN TestGetFirst
getSecond returned: mocked
--- PASS: TestGetFirst (0.00s)
=== RUN TestGetSecond
--- PASS: TestGetSecond (0.00s)
PASS