英文:
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
}
在这个文件中,我们可以找到需要模拟的方法。这些方法是:GetByID
和Update
。显然,这不是你实际的代码,但现在并不重要。
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!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论