Testing my interface in golang with mocks. Specifically test 1 function that calls a sibling function

huangapple go评论81阅读模式
英文:

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() {}

在这里,我进行了一些更改,以便能够成功模拟我们的依赖关系。让我总结一下:

  1. MyStruct 有一个类型为 MyInterface 的依赖项。
  2. NewMyStruct 函数接受接口作为参数,并返回指向 MyStruct 结构体的指针。
  3. 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:

  1. The MyStruct has a dependency of type MyInterface
  2. The function NewMyStruct accepts the interface as parameter and returns a pointer to the MyStruct struct
  3. In the Foo method, we're going to rely on the DS 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!

huangapple
  • 本文由 发表于 2023年2月15日 23:27:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/75461990.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定