英文:
Testing my interface in golang with mocks. Specifically test 1 function that calls a sibling function
问题
假设我有这段代码,并且我想为**Foo()**创建一个测试。
重要的部分是Foo调用了Bar
package main
type MyInterface interface {
Foo() error
Bar() error
}
type MyStruct struct {
}
func NewMyStruct() MyInterface{
return &MyStruct{}
}
func (m *MyStruct) Foo() error {
// 做一些事情
m.Bar()
// 做一些其他事情
return nil
}
func (m *MyStruct) Bar() error {
// 做一些事情
return nil
}
在运行Foo之前,是否有可能创建一个测试,以模拟Bar的行为?或者我做错了什么根本性的事情?
我可以看到,如果我将Bar提取到自己的服务中,我可以以这种方式模拟它,但这也不太对。
如果你有任何见解或者文档链接,都会很好。
英文:
Let's say I have this code and I want to create a test for Foo()
The important part it Foo makes a call to Bar
package main
type MyInterface interface {
Foo() error
Bar() error
}
type MyStruct struct {
}
func NewMyStruct() MyInterface{
return &MyStruct{}
}
func (m *MyStruct) Foo() error {
// do something
m.Bar()
// do something else
return nil
}
func (m *MyStruct) Bar() error {
// do something
return nil
}
Is it possible to create a test for this where I can mock the behaviour of Bar before I run Foo? or am I doing something fundamentally wrong?
I can see that if I extracted Bar into its own service I would mock it that way but that also doesn't feel right.
Any insights or links to documentation would be great.
答案1
得分: 3
你应该能够通过几个更改来实现你的需求。首先,让我呈现代码,然后我会逐步介绍所有相关的更改。
main.go
文件
package main
type MyInterface interface {
Foo() error
Bar() error
}
type MyStruct struct {
DS MyInterface
}
// 在这里,你需要依赖一个接口并返回一个指向结构体的指针
func NewMyStruct(ds MyInterface) *MyStruct {
return &MyStruct{
DS: ds,
}
}
func (m *MyStruct) Foo() error {
// 做一些事情
m.DS.Bar()
// 做一些其他事情
return nil
}
func (m *MyStruct) Bar() error {
// 做一些事情
return nil
}
func main() {}
在这里,我进行了一些更改,以便能够成功模拟我们的依赖关系。让我总结一下:
MyStruct
有一个类型为MyInterface
的依赖项。NewMyStruct
函数接受接口作为参数,并返回指向MyStruct
结构体的指针。- 在
Foo
方法中,我们将依赖于指针接收器类型实例的DS
字段。
现在,让我们切换到测试文件。
main_test.go
文件
package main
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// 1. 声明模拟对象
type myStructMock struct {
mock.Mock
}
// 2. 实现接口
func (m *myStructMock) Foo() error {
args := m.Called()
return args.Error(0)
}
func (m *myStructMock) Bar() error {
args := m.Called()
return args.Error(0)
}
func TestFoo(t *testing.T) {
// 3. 实例化/设置模拟对象
myStructMock := new(myStructMock)
myStructMock.On("Bar").Return(nil).Times(1)
sut := NewMyStruct(myStructMock)
err := sut.Foo()
// 4. 检查模拟对象的所有期望是否都满足
assert.Nil(t, err)
assert.True(t, myStructMock.AssertExpectations(t))
}
在这里,我发现在代码中添加注释可以按照时间顺序给你一个发生的顺序。这个想法是,被测试的真实系统(例如 sut
变量)依赖于模拟对象而不是实际的实现。通过使用 Times
方法,我们确保 Bar
方法只被调用一次。
当涉及测试生产代码时,我总是使用这种方法,我觉得它非常灵活和令人惊叹!如果这对你有帮助,或者你还需要其他东西,请告诉我,谢谢!
英文:
You should be able to achieve what you need with a couple of changes. First, let me present the code and then I'll walk you through all of the relevant changes.
main.go
file
package main
type MyInterface interface {
Foo() error
Bar() error
}
type MyStruct struct {
DS MyInterface
}
// here you've to depend upon an interface and return a pointer to a struct
func NewMyStruct(ds MyInterface) *MyStruct {
return &MyStruct{
DS: ds,
}
}
func (m *MyStruct) Foo() error {
// do something
m.DS.Bar()
// do something else
return nil
}
func (m *MyStruct) Bar() error {
// do something
return nil
}
func main() {}
Here, I changed a couple of things in order to be able to successfully mock our dependencies. Let me recap them:
- The
MyStruct
has a dependency of typeMyInterface
- The function
NewMyStruct
accepts the interface as parameter and returns a pointer to theMyStruct
struct - In the
Foo
method, we're going to rely on theDS
field of our pointer receiver type instance
Now, let's switch to the test file.
main_test.go
file
package main
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// 1. declare mock
type myStructMock struct {
mock.Mock
}
// 2. implement the interface
func (m *myStructMock) Foo() error {
args := m.Called()
return args.Error(0)
}
func (m *myStructMock) Bar() error {
args := m.Called()
return args.Error(0)
}
func TestFoo(t *testing.T) {
// 3. instantiate/setup mock
myStructMock := new(myStructMock)
myStructMock.On("Bar").Return(nil).Times(1)
sut := NewMyStruct(myStructMock)
err := sut.Foo()
// 4. check that all expectations were met on the mock
assert.Nil(t, err)
assert.True(t, myStructMock.AssertExpectations(t))
}
Here, I found it best to add comments within the code to give you a chronological order of what's going on. The idea is that the real system tested (e.g. the sut
variable) is relying on mock instead of actual implementations. Thanks to the method Times
, we make sure that the Bar
method is called once.
I always used this approach when it comes to testing production code and I find it pretty flexible and amazing!
Let me know if this helps you or if you still need something else, thanks!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论