You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Summary
Some of our tests use shared mock instances across subtests within a test suite. This could cause expectations set from subtest A still leave open in subtest B if it's not fulfilled in subtest A. This is due to a limitation from testify that before/after test hook is not available for subtest. Currently, we avoid that issue by ensuring the number of expectations matches the actual executions using .Once(). But a similar issue still could happen in parallel tests.
The second issue is in a complex function that makes many calls from dependencies, we had to set the expectations for all executed calls in each subtest resulting in the code being not DRY and making the test harder to read/understand.
Proposed solution
In testify v1.8.2, before and after test hooks are now supported for subtests. We can use this to ensure every subtest starts with empty expectations.
To also fix the second issue as well as for the parallel testing, mocks need to be decoupled from the suite struct and can be initialized at anytime when needed.
Proposing following approach:
package appeal_test
typemockstruct {
mockRepository*appeal.RepositorymockXService*appeal.XServiceservice*appeal.Service
}
funcnewMock() mock {
m:=mock{
mockRepository: new(mockRepository),
mockXService: new(mockXService),
}
m.service=appeal.NewService(m.mockRepository, m.mockXService)
returnm
}
func (m*mock) setSuccessExpectations() {
// always expect mock.Anything in params and return success valuesm.mockRepository.EXPECT().Create(mock.Anything, mock.Anything).Return(nil)
m.mockXService.EXPECT().Create(mock.Anything, mock.Anything).Return(nil)
}
typeAppealTestSuitestruct {
suite.Suitemock// shared mock
}
func (s*AppealTestSuite) SetupSubTest() {
s.mock=newMock() // reinitialize mock for each sub test
}
// usage examplefunc (s*AppealTestSuite) TestCreate() {
s.Run("repository returns an error", func() {
s.mockRepository.EXPECT().Create(mock.Anything, mock.Anything).Return(errors.New("error"))
_, err:=s.service.Create(context.Background(), appeal.CreateRequest{})
s.Error(err)
})
s.Run("test specific case", func() {
s.setSuccessExpectations() // set common expectations// only specify expectations related to the tested case:s.mockXService.EXPECT().Create(mock.Anything, mock.Anything).Return(errors.New("error"))
_, err:=s.service.Create(context.Background(), appeal.CreateRequest{})
s.Error(err)
})
s.Run("parallel tests", func() {
testCases:= []struct {}{
// ...
}
for_, tc:=rangetestCases {
tc:=tcs.Run(tc.name, func() {
s.T().Parallel()
independentMock:=newMock()
// set expectations for independentMock// ...//_, err := independentMock.service.Create(context.Background(), appeal.CreateRequest{})
})
}
}
}
decoupled mocks from suite struct into an independent struct
restart mocks in every subtests s.mock = newMock()
for parallel tests, use an independent mock instead of a shared one
introduced setSuccessExpectations() to avoid writing common expectations multiple times across subtests
Note: currently trying out this approach for appeal service as a POC, will raise the PR sson
The text was updated successfully, but these errors were encountered:
func (m*mock) setSuccessExpectations() {
// always expect mock.Anything in params and return success valuesm.mockRepository.EXPECT().Create(mock.Anything, mock.Anything).Return(nil)
m.mockXService.EXPECT().Create(mock.Anything, mock.Anything).Return(nil)
}
I don't think we should have something like this which always expects only mock.Anything. We can try to abstract common expectations, but that should not come at a cost of proper expectation checks.
I understand that we do use mock.Anything pervasively in our code, but that is something that should change rather than be embraced.
Summary
Some of our tests use shared mock instances across subtests within a test suite. This could cause expectations set from subtest A still leave open in subtest B if it's not fulfilled in subtest A. This is due to a limitation from testify that before/after test hook is not available for subtest. Currently, we avoid that issue by ensuring the number of expectations matches the actual executions using
.Once()
. But a similar issue still could happen in parallel tests.The second issue is in a complex function that makes many calls from dependencies, we had to set the expectations for all executed calls in each subtest resulting in the code being not DRY and making the test harder to read/understand.
Proposed solution
In testify v1.8.2, before and after test hooks are now supported for subtests. We can use this to ensure every subtest starts with empty expectations.
To also fix the second issue as well as for the parallel testing, mocks need to be decoupled from the suite struct and can be initialized at anytime when needed.
Proposing following approach:
s.mock = newMock()
setSuccessExpectations()
to avoid writing common expectations multiple times across subtestsNote: currently trying out this approach for appeal service as a POC, will raise the PR sson
The text was updated successfully, but these errors were encountered: