In this example we are going to test a function but by mocking it. Mocking is important because sometimes we don't want to run the real logic of a given function for any given reason.


Installation


Install mockery binary to OS with go get github.com/vektra/mockery/v2/.../ command first. This will also add relevant entries to go.mod/sum but you can remove them all because you will have to run go get -u http://github.com/stretchr/testify command which will be enough.


Example


package file

import (
"image"
"mime"
"os"

_ "image/gif"
_ "image/jpeg"
_ "image/png"
)

type Manager interface {
Mime(path string) (string, error)
}

type Image struct {}

func (Image) Mime(path string) (string, error) {
fil, err := os.Open(path)
if err != nil {
return "", err
}
defer fil.Close()

_, mim, err := image.DecodeConfig(fil)
if err != nil {
return "", err
}

return mime.TypeByExtension("." + mim), nil
}

Test


There is a Manager interface in ./internal/file/file.go file and we are creating its mock ./internal/file/mocks/manager.go file.


$ mockery --name Manager --dir ./internal/file/ --output ./internal/file/mocks --filename manager.go

22 Jul 20 10:21 BST INF Starting mockery dry-run=false version=(devel)
22 Jul 20 10:21 BST INF Walking dry-run=false version=(devel)
22 Jul 20 10:21 BST INF Generating mock dry-run=false interface=Manager qualified-name=github.com/you/client/internal/file version=(devel)

package file

import (
"fmt"
"testing"

"github.com/you/client/internal/file/mocks"

"github.com/stretchr/testify/assert"
)

func TestFile_Mime(t *testing.T) {
mockFile := &mocks.Manager{}

t.Run("file not found error", func(t *testing.T) {
mockFile.
On("Mime", "some-file.png").
Once().
Return("", fmt.Errorf("file not found"))

mim, err := mockFile.Mime("some-file.png")

assert.Empty(t, mim)
assert.Error(t, err, "file not found")
})

t.Run("config decoding error", func(t *testing.T) {
mockFile.
On("Mime", "some-file.png").
Once().
Return("", fmt.Errorf("config error"))

mim, err := mockFile.Mime("some-file.png")

assert.Empty(t, mim)
assert.Error(t, err, "config error")
})

t.Run("successfully getting mime", func(t *testing.T) {
mockFile.
On("Mime", "some-file.png").
Once().
Return("image/png", nil)

mim, err := mockFile.Mime("some-file.png")

assert.Equal(t, "image/png", mim)
assert.NoError(t, err)
})
}

$ go test -v -run TestFile_Mime ./internal/file/

=== RUN TestFile_Mime
=== RUN TestFile_Mime/file_not_found_error
=== RUN TestFile_Mime/config_decoding_error
=== RUN TestFile_Mime/successfully_getting_mime
--- PASS: TestFile_Mime (0.00s)
--- PASS: TestFile_Mime/file_not_found_error (0.00s)
--- PASS: TestFile_Mime/config_decoding_error (0.00s)
--- PASS: TestFile_Mime/successfully_getting_mime (0.00s)
PASS

Update


You can install mockery with Homebrew on Mac. Also, now you don't have to add mockery annotations to interfaces in the code anymore. You can run mockery --help for all flags but here is the mostly used commands.


--keeptree: Matches the actual package structure for the mock files
--inpackage: Uses the package name rather than "mocks"
--exported: Creates exported versions of unexported interfaces
--all: Creates mocks for all interfaces in all sub directories
--case=snake: Uses snake case for the file names

-- In root
- Runs through all interfaces in the codebase and puts mocks in root level "mocks" folder.
$ mockery --keeptree --inpackage --exported --all --case=snake

-- In package (classic)
- Runs through all interfaces in a package folder and puts mocks in "mocks" folder of same package folder.
$ mockery --exported --all --case=snake --dir=payment/ --output=payment/mocks

If you prefer running a common command $ go generate ./... to generate all mocks in one-go, you can try version below.


//go:generate mockery --exported --case snake --name Doer
type interface Doer {
...