22/07/2020 - GO
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.
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.
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
}
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
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 {
...