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.
CodePudding user response:
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!