英文:
Is it possible to mock a function imported from a package?
问题
我有以下要测试的方法,它使用了从一个包中导入的函数。
import x.y.z
func abc() {
...
v := z.SomeFunc()
...
}
在Go语言中,是否可以模拟SomeFunc()
函数?
英文:
I have the following method to test, which uses a function imported from a package.
import x.y.z
func abc() {
...
v := z.SomeFunc()
...
}
Is it possible to mock SomeFunc()
in Go?
答案1
得分: 82
是的,可以通过简单的重构实现。创建一个函数类型的zSomeFunc
变量,并用z.SomeFunc
进行初始化,然后在你的包中调用该变量,而不是z.SomeFunc()
:
var zSomeFunc = z.SomeFunc
func abc() {
// ...
v := zSomeFunc()
// ...
}
在测试中,你可以将另一个函数赋值给zSomeFunc
,这个函数在测试中定义,并根据测试的需要执行相应的操作。
例如:
func TestAbc(t *testing.T) {
// 保存当前函数并在最后恢复:
old := zSomeFunc
defer func() { zSomeFunc = old }()
zSomeFunc = func() int {
// 这个函数将被调用,你可以在这里执行任何操作,
// 并返回任何你想要的结果
return 1
}
// 调用被测试的函数
abc()
// 检查预期的行为
}
英文:
Yes, with a simple refactoring. Create a zSomeFunc
variable of function type, initialized with z.SomeFunc
, and have your package call that instead of z.SomeFunc()
:
var zSomeFunc = z.SomeFunc
func abc() {
// ...
v := zSomeFunc()
// ...
}
In tests you may assign another function to zSomeFunc
, one that is defined in tests, and does whatever the test wants it to.
For example:
func TestAbc(t *testing.T) {
// Save current function and restore at the end:
old := zSomeFunc
defer func() { zSomeFunc = old }()
zSomeFunc = func() int {
// This will be called, do whatever you want to,
// return whatever you want to
return 1
}
// Call the tested function
abc()
// Check expected behavior
}
See related / possible duplicate:
https://stackoverflow.com/questions/40615641/testing-os-exit-scenarios-in-go-with-coverage-information-coveralls-io-goverall/40801733#40801733
答案2
得分: 12
你可以这样做:
import "x/y/z"
var someFunc = z.SomeFunc
func abc() {
...
v := someFunc()
...
}
在你的测试文件中,你可以这样做:
func Test_abc() {
someFunc = mockFunc
abc()
}
但请确保以并发的方式进行操作,如果你有多个TestXxx
函数调用abc
或设置someFunc
,最好使用一个带有someFunc
字段的struct
。
英文:
One thing you can do is this:
import "x/y/z"
var someFunc = z.SomeFunc
func abc() {
...
v := someFunc()
...
}
And in your test file you would do this.
func Test_abc() {
someFunc = mockFunc
abc()
}
But make sure that you do this in a concurrent manner, if you have multiple TestXxx
functions calling abc
or setting someFunc
you may be better of using a struct
with a someFunc
field.
答案3
得分: 5
使用函数指针和猴子补丁是其中一种方法。但是当你需要模拟多个函数时,你将会有多个函数指针,并且你必须通过函数指针来调用函数,这是不必要的。
更好且推荐的方法是创建一个接口,并将你的函数作为实现该接口的结构体的一部分。完成后,你可以使用一些适用于Go的好工具来生成模拟。
我一直在使用这个命令:
mockgen -source myModule.go -package myPackage -destination myModuleMock.go
你可以通过以下方式安装它:
go get github.com/golang/mock
英文:
Having function pointer and monkey patching it is the one of doing it. But then when you multiple functions to mock, you will have a number function pointers and unnecessarily you will have to call function using the function pointer only.
The better and the recommended idea to have an interface and make your function part of the struct implementing the interface. Once done that, you can generate mocks using some nice tools available for go.
I have been using this:
mockgen -source myModule.go -package myPackage -destination myModuleMock.go
You can install it by:
go get github.com/golang/mock
答案4
得分: 2
创建一个包级变量是可行的选择,但它也有一些限制。举几个例子:
- 它不鼓励使用
t.Parallel()
来并行运行测试,因为在模拟函数的不同行为之间可能会存在竞争条件。 - 它是危险的,因为将来在同一个包中的开发人员可能会意外地更新这个全局变量的实现。
另一种方法是将你想要模拟的方法作为参数传递给函数,以实现可测试性。在我的情况下,我已经有很多客户端调用这个方法,因此我希望避免违反现有的契约,所以我最终创建了一个包装函数。
例如:
import (
z "x.y.z"
)
// 这应该复制 x.y.z 中函数 z 的类型
type operation func() resp
func wrappedAbc(op operation) {
....
resp := op()
....
}
func Abc() {
wrappedAbc(z)
}
现在,为了测试实际的逻辑,你将测试对 wrappedAbc
的调用,而不是 abc
,并且你将传递一个模拟的 operation
。这将允许你测试所有的业务逻辑,同时不违反方法 Abc
的当前客户端的 API 契约。
英文:
While creating a package level variable is a viable option, it comes with some restrictions. to name a few:
- It discourages running parallel tests using
t.Parallel()
as there can be race conditions on different behaviors of mocked function. - it is dangerous as a future developer in the same package can accidentally update the implementation of this global variable.
another way is to pass along the methods you want to mock as arguments to the function to enable testability. In my case, I already had numerous clients calling this method and thus, I wanted to avoid violating the existing contracts. so, I ended up creating a wrapped function.
eg:
import (
z "x.y.z"
)
//this should replicate the type of function z from x.y.z
type operation func() resp
func wrappedAbc(op operation) {
....
resp := op()
....
}
func Abc() {
wrappedAbc(z)
}
now for testing the actual logic, you will test calls to wrappedAbc
instead of abc
and you would pass it a mocked operation
. this will allow you to test all the business logic while not violating API contract with current clients of method Abc
.
答案5
得分: 1
mockcompose
使用一种方法,允许你生成一个模拟类,你可以指示mockcompose
包含你选择的依赖闭包(来自其他包的任何导入函数)。同时,它会生成一个带有本地覆盖的主题函数的克隆副本,以便你可以进行测试。你可以使用go generate
嵌入代码生成过程,以确保你的克隆副本始终与代码更改保持同步。
假设你有一个函数functionThatUsesGlobalFunction
,它导入了fmt
包中的Sprintf
。
func functionThatUsesGlobalFunction(
format string,
args ...interface{},
) string {
//
// 跳过复杂的逻辑...
//
// 调用fmt包中的全局函数
return fmt.Sprintf(format, args...)
}
你的目标是使用模拟的fmt
包中的Sprintf
来测试functionThatUsesGlobalFunction
。
为此,你可以使用以下配置将mockcompose
与go generate
一起使用:
mocks.go
//go:generate mockcompose -n mockFmt -p fmt -mock Sprintf
//go:generate mockcompose -n mockJson -p encoding/json -mock Marshal
//go:generate mockcompose -n clonedFuncs -real "functionThatUsesGlobalFunction,fmt=fmtMock"
package clonefn
然后,go generate mockcompose
将为你生成必要的类,使你能够编写以下测试代码:
package clonefn
import (
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
var fmtMock *mockFmt = &mockFmt{}
func TestClonedFuncs(t *testing.T) {
assert := require.New(t)
// 设置函数的模拟
fmtMock.On("Sprintf", mock.Anything, mock.Anything).Return("mocked Sprintf")
// 在functionThatUsesMultileGlobalFunctions内部:fmt.Sprintf被模拟
assert.True(functionThatUsesGlobalFunction_clone("format", "value") == "mocked Sprintf")
}
请查看这里获取详细信息。
英文:
mockcompose
uses an approach that allows you to generate a mocking class, you can direct mockcompose
to include your selected dependency closure (any imported functions from other packages). In the mean time, it generates a cloned copy of your subject function with local overrides so that you can test against. You can embed code generation process with go generate
, therefore ensure your cloned copy is always in-sync with your code changes.
Say you have a function functionThatUsesGlobalFunction
that imports
Sprintf
in package fmt
.
func functionThatUsesGlobalFunction(
format string,
args ...interface{},
) string {
//
// skip fansy logic...
//
// call out to a global function in fmt package
return fmt.Sprintf(format, args...)
}
Your goal is to test functionThatUsesGlobalFunction
with Sprintf
in package fmt
being mocked.
To do that, you can configure go generate
with mockcompose
as following:
mocks.go
//go:generate mockcompose -n mockFmt -p fmt -mock Sprintf
//go:generate mockcompose -n mockJson -p encoding/json -mock Marshal
//go:generate mockcompose -n clonedFuncs -real "functionThatUsesGlobalFunction,fmt=fmtMock"
package clonefn
go generate mockcompose
will then generate plumbing classes for you, enable you to write test code as following:
package clonefn
import (
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
var fmtMock *mockFmt = &mockFmt{}
func TestClonedFuncs(t *testing.T) {
assert := require.New(t)
// setup function mocks
fmtMock.On("Sprintf", mock.Anything, mock.Anything).Return("mocked Sprintf")
// inside functionThatUsesMultileGlobalFunctions: fmt.Sprintf is mocked
assert.True(functionThatUsesGlobalFunction_clone("format", "value") == "mocked Sprintf")
}
Please checkout this for details.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论