在Golang的单元测试中使用mocks(附实例)

969 阅读1分钟

在这个例子中,我们要测试一个函数,但要通过嘲弄它。嘲弄是很重要的,因为有时我们不想因为任何特定的原因而运行一个特定函数的真实逻辑。

安装

首先用go get github.com/vektra/mockery/v2/.../ 命令将mockery 二进制文件安装到操作系统。这也会给go.mod/sum 添加相关的条目,但你可以把它们全部删除,因为你必须运行go get -u http://github.com/stretchr/testify 命令,这就足够了。

例子

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
}

测试

./internal/file/file.go 文件中有一个Manager 接口,我们正在创建其模拟的./internal/file/mocks/manager.go 文件。

$ 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

使现代化

你可以在Mac上用自制软件安装Mockry。此外,现在您不必再向代码中的接口添加模仿注释。您可以运行mockry——所有标志的帮助,但这里是最常用的命令。

--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