assert: mock: I don't know what to return because the method call was unexpected Error while writing unit test in Go

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

assert: mock: I don't know what to return because the method call was unexpected Error while writing unit test in Go

问题

我正在使用Go语言中的testify来编写我的服务方法的单元测试,除了更新方法之外,所有方法都正常工作。因为在更新方法中,我在Update方法内部调用了同一个服务的另一个方法("GetByID")。

我的服务中Update方法的实现如下:

func (ts *teamService) Update(team *team.Team) AppError {
    t, err := ts.TeamRepo.GetByID(team.ID)
    if err != nil {
        return err
    }
    if t.TeamOwnerID != team.TeamOwnerID {
        return NewForbiddenError(forbiddenErr)
    }

    return ts.TeamRepo.Update(team)
}

更新的MockRepo方法:

func (t *teamRepoMock) Update(team *team.Team) AppError {
    args := t.Called(team)
    if args.Error(0) != nil {
        return NewNotFoundError(args.Error(0))
    }

    return nil
}

测试的实现:

func TestUpdate(t *testing.T) {
    _, teamIdGen, playerIdGen := setupConfig()

    t.Run("Update a team", func(t *testing.T) {
        teamRepo, _, ts := setupTeamService(teamIdGen, playerIdGen)

        teamRepo.On("Update", testTeam1).Return(nil)
        result := ts.Update(testTeam1)

        assert.Nil(t, result)
    })

    t.Run("Update a team fails", func(t *testing.T) {
        teamRepo, _, ts := setupTeamService(teamIdGen, playerIdGen)

        expected := oopsErr
        teamRepo.On("Update", testTeam1).Return(expected)
        result := ts.Update(testTeam1)

        assert.EqualValues(t, expected.Error(), result.Error())
    })
}

现在当我运行测试时,我得到以下错误:

--- FAIL: TestUpdate (0.01s)
    --- FAIL: TestUpdate/Update_a_team (0.01s)
panic: 
assert: mock: I don't know what to return because the method call was unexpected.
    Either do Mock.On("GetByID").Return(...) first, or remove the GetByID() call.
    This method was unexpected:
        GetByID(string)
        0: ""
at: [/home/waleem/Desktop/project/eazykhel_server/services/teamservice/team_service_init_test.go:18 /home/waleem/Desktop/project/eazykhel_server/services/teamservice/team_service.go:146 /home/waleem/Desktop/project/eazykhel_server/services/teamservice/team_service_test.go:277] [recovered]
panic:

我尝试在测试函数实现中在调用.On("Update")之前和之后调用mock.On("GetByID"),但没有成功,我还修改了mockRepo的Update函数,但也没有成功。

英文:

I am using testify in Go to write unit tests for my service methods and all the methods are working fine excepted the update method because in the update method I call another method("GetByID") of the same service inside the Update method.

Implementation of Update method in my service:

func (ts *teamService) Update(team *team.Team) AppError {
	t, err := ts.TeamRepo.GetByID(team.ID)
	if err != nil {
		return err
	}
	if t.TeamOwnerID != team.TeamOwnerID {
		return NewForbiddenError(forbiddenErr)
	}

	return ts.TeamRepo.Update(team)
}

MockRepo method for update:

func (t *teamRepoMock) Update(team *team.Team) AppError {
	args := t.Called(team)
	if args.Error(0) != nil {
		return NewNotFoundError(args.Error(0))
	}

	return nil
}

Implementation of the test:

func TestUpdate(t *testing.T) {
	_, teamIdGen, playerIdGen := setupConfig()

	t.Run("Update a team", func(t *testing.T) {
		teamRepo, _, ts := setupTeamService(teamIdGen, playerIdGen)

		teamRepo.On("Update", testTeam1).Return(nil)
		result := ts.Update(testTeam1)

		assert.Nil(t, result)
	})

	t.Run("Update a team fails", func(t *testing.T) {
		teamRepo, _, ts := setupTeamService(teamIdGen, playerIdGen)

		expected := oopsErr
		teamRepo.On("Update", testTeam1).Return(expected)
		result := ts.Update(testTeam1)

		assert.EqualValues(t, expected.Error(), result.Error())
	})
}

Now when I run the test I get the following error:

--- FAIL: TestUpdate (0.01s)
    --- FAIL: TestUpdate/Update_a_team (0.01s)
panic: 
assert: mock: I don't know what to return because the method call was unexpected.
	Either do Mock.On("GetByID").Return(...) first, or remove the GetByID() call.
	This method was unexpected:
		GetByID(string)
		0: ""
	at: [/home/waleem/Desktop/project/eazykhel_server/services/teamservice/team_service_init_test.go:18 /home/waleem/Desktop/project/eazykhel_server/services/teamservice/team_service.go:146 /home/waleem/Desktop/project/eazykhel_server/services/teamservice/team_service_test.go:277] [recovered]
	panic:

I tried calling mock.On("GetByID") before and after I call .On("Update") in my test function implementation and it didn't work and also I modified the mockRepo Update function but it didn't work.

答案1

得分: 1

让我试着帮你找出问题所在。我复制了一份仓库,并进行了一些简化,只发布了相关的代码。如果我没有理解错,你的解决方案中有一个服务(TeamService),它调用了底层包(TeamRepo)提供的一些方法。你想要测试TeamService结构体的Update方法。在这个回顾之后,让我先介绍一下代码,然后我会尝试解释每个文件:

repo/repo.go

package repo

type Team struct {
	ID          int
	TeamOwnerID int
	Name        string
}

type TeamRepo struct{}

func (t *TeamRepo) GetByID(id int) (Team, error) {
	return Team{ID: id, TeamOwnerID: id, Name: "MyTeam"}, nil
}

func (t *TeamRepo) Update(team Team) error {
	return nil
}

在这个文件中,我们可以找到需要模拟的方法。这些方法是:GetByIDUpdate。显然,这不是你实际的代码,但现在并不重要。

services/service.go

package services

import (
	"errors"

	"testifymock/repo"
)

type TeamService struct {
	TR TeamRepoInterface
}

func NewTeamService(repo TeamRepoInterface) *TeamService {
	return &TeamService{
		TR: repo,
	}
}

type TeamRepoInterface interface {
	GetByID(id int) (repo.Team, error)
	Update(team repo.Team) error
}

func (ts *TeamService) Update(team *repo.Team) error {
	t, err := ts.TR.GetByID(team.ID)
	if err != nil {
		return err
	}

	if t.TeamOwnerID != team.TeamOwnerID {
		return errors.New("forbidden")
	}

	return ts.TR.Update(*team)
}

在这里,我们可以看到这个服务将成为我们测试代码中的被测系统(System Under Test,sut)。通过依赖注入,我们将利用通过接口TeamRepoInterface注入的repo包提供的功能。

services/service_test.go

package services

import (
	"errors"
	"testing"

	"testifymock/repo"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

// 1. 声明模拟结构体
type teamRepoMock struct {
	mock.Mock
}

// 2. 实现接口
func (m *teamRepoMock) GetByID(id int) (repo.Team, error) {
	args := m.Called(id)

	return args.Get(0).(repo.Team), args.Error(1)
}

func (m *teamRepoMock) Update(team repo.Team) error {
	args := m.Called(team)

	return args.Error(0)
}

func TestUpdate(t *testing.T) {
	t.Run("GoodUpdate", func(t *testing.T) {
		// 3. 实例化/设置模拟
		repoMock := new(teamRepoMock)
		repoMock.On("GetByID", 1).Return(repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"}, nil).Times(1)
		repoMock.On("Update", repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"}).Return(nil).Times(1)

		sut := NewTeamService(repoMock)
		err := sut.Update(&repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"})

		// 4. 检查模拟是否满足所有期望
		assert.Nil(t, err)
		assert.True(t, repoMock.AssertExpectations(t))
	})

	t.Run("BadUpdate", func(t *testing.T) {
		// 3. 实例化/设置模拟
		repoMock := new(teamRepoMock)
		repoMock.On("GetByID", 1).Return(repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"}, nil).Times(1)
		repoMock.On("Update", repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"}).Return(errors.New("some error while updating")).Times(1)

		sut := NewTeamService(repoMock)
		err := sut.Update(&repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"})

		// 4. 检查模拟是否满足所有期望
		assert.Equal(t, "some error while updating", err.Error())
		assert.True(t, repoMock.AssertExpectations(t))
	})
}

在代码中,你可以找到一些注释,以更好地详细说明正在发生的事情。正如你猜到的那样,问题在于你的代码中缺少了这个调用:

repoMock.On("GetByID", 1).Return(repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"}, nil).Times(1)

如果你运行我的解决方案,它应该也适用于你。如果这解决了你的问题,或者还有其他问题,请告诉我!

英文:

Let me try to help you in figuring out the problem. I reproduced the repo with some simplification just to post the relevant code. If I'm not wrong in your solution there is a service (TeamService) that invokes some methods provided by an underlying package (TeamRepo). You wanna test the method Update method of the TeamService struct. After this recap, let me present the code first and I'll try to explain each file:

repo/repo.go

package repo

type Team struct {
	ID          int
	TeamOwnerID int
	Name        string
}

type TeamRepo struct{}

func (t *TeamRepo) GetByID(id int) (Team, error) {
	return Team{ID: id, TeamOwnerID: id, Name: "MyTeam"}, nil
}

func (t *TeamRepo) Update(team Team) error {
	return nil
}

Within this file, we can find the methods that we've to mock out. The methods are: GetByID and Update. Obviously, this is not your actual code but it doesn't matter for now.

services/service.go

package services

import (
	"errors"

	"testifymock/repo"
)

type TeamService struct {
	TR TeamRepoInterface
}

func NewTeamService(repo TeamRepoInterface) *TeamService {
	return &TeamService{
		TR: repo,
	}
}

type TeamRepoInterface interface {
	GetByID(id int) (repo.Team, error)
	Update(team repo.Team) error
}

func (ts *TeamService) Update(team *repo.Team) error {
	t, err := ts.TR.GetByID(team.ID)
	if err != nil {
		return err
	}

	if t.TeamOwnerID != team.TeamOwnerID {
		return errors.New("forbidden")
	}

	return ts.TR.Update(*team)
}

Here, we can see the service that will be our System Under Test (sut) in our test code. Through Dependency Injection, we'll take advantage of the capabilities provided by the repo package that is injected via the interface TeamRepoInterface.

services/service_test.go

package services

import (
	"errors"
	"testing"

	"testifymock/repo"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

// 1. declare the mock struct
type teamRepoMock struct {
	mock.Mock
}

// 2. implement the interface
func (m *teamRepoMock) GetByID(id int) (repo.Team, error) {
	args := m.Called(id)

	return args.Get(0).(repo.Team), args.Error(1)
}

func (m *teamRepoMock) Update(team repo.Team) error {
	args := m.Called(team)

	return args.Error(0)
}

func TestUpdate(t *testing.T) {
	t.Run("GoodUpdate", func(t *testing.T) {
		// 3. instantiate/setup mock
		repoMock := new(teamRepoMock)
		repoMock.On("GetByID", 1).Return(repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"}, nil).Times(1)
		repoMock.On("Update", repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"}).Return(nil).Times(1)

		sut := NewTeamService(repoMock)
		err := sut.Update(&repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"})

		// 4. check that all expectations were met on the mock
		assert.Nil(t, err)
		assert.True(t, repoMock.AssertExpectations(t))
	})

	t.Run("BadUpdate", func(t *testing.T) {
		// 3. instantiate/setup mock
		repoMock := new(teamRepoMock)
		repoMock.On("GetByID", 1).Return(repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"}, nil).Times(1)
		repoMock.On("Update", repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"}).Return(errors.New("some error while updating")).Times(1)

		sut := NewTeamService(repoMock)
		err := sut.Update(&repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"})

		// 4. check that all expectations were met on the mock
		assert.Equal(t, "some error while updating", err.Error())
		assert.True(t, repoMock.AssertExpectations(t))
	})
}

Within the code, you can find some comments to better detail what's going on. As you've guessed the issue was this missing call in your code:
> repoMock.On("GetByID", 1).Return(repo.Team{ID: 1, TeamOwnerID: 1, Name: "test"}, nil).Times(1)

If you run my solution, it should work also for you.
Let me know if this solves your issue or if there are any other problems!

huangapple
  • 本文由 发表于 2022年12月28日 13:59:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/74936745.html
匿名

发表评论

匿名网友

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

确定