I have a go method which is responsible for sending OTP messages to users. It calls four methods inside (whom i am saying sub methods) RemoveOTP, GetOneTimePassCode, SaveOTP and SendOTP .
Filename : user_usecase.go
func (u *UserUseCase) SendUserOTP(ctx context.Context, user *domain.User, mobileNumber string) error {
var err error
var OTPNumber string
var expiration time.Time
//Remove previous OTP if any in DB
if err = u.Repo.RemoveOTP(ctx, user.ID, mobileNumber); err != nil {
return err
}
//Generate OTP
OTPNumber, expiration, err = u.TokenService.GetOneTimePassCode()
if err != nil {
return err
}
//Save Code in Database and then send
otpDataInput := domain.UserMobileOtps{
UserID: user.ID,
MobileNumber: mobileNumber,
Email: user.Email,
OtpCode: OTPNumber,
ExpiredAt: expiration,
}
if err = u.Repo.SaveOTP(ctx, otpDataInput); err != nil {
return err
}
if err = u.SMSService.SendOTP(ctx, mobileNumber, OTPNumber); err != nil {
return err
}
return nil
}
File for test cases: user_usecase_test.go and unit test case error method
// setup initializes common variables and returns them.
func setup(t *testing.T) (context.Context,
*gomock.Controller,
*mocks.MockUserRepository,
usecase.UserUseCase,
domain.User,
*mocks.MockTokenService,
*mocks.MockSMSService,
) {
ctx := context.Background()
ctrl := gomock.NewController(t)
mockRepo := mocks.NewMockUserRepository(ctrl)
mockTokenService := mocks.NewMockTokenService(ctrl)
mockSmsService := mocks.NewMockSMSService(ctrl)
uc := usecase.UserUseCase{Repo: mockRepo, TokenService: mockTokenService, SMSService: mockSmsService}
user := domain.User{ID: 1}
return ctx, ctrl, mockRepo, uc, user, mockTokenService, mockSmsService
}
func TestSendUserOTP_Error(t *testing.T) {
ctx, ctrl, mockRepo, uc, user, mockTokenService, mockSmsService := setup(t)
otpExpiration := time.Now().Add(time.Minute * 5)
defer ctrl.Finish()
someError := errors.New("some issue")
mockRepo.EXPECT().RemoveOTP(ctx, user.ID, "XXXXXYYYYY").Return(someError).Times(1)
mockTokenService.EXPECT().GetOneTimePassCode().Return("XXXYYY", otpExpiration, someError).Times(1)
mockRepo.EXPECT().SaveOTP(ctx, domain.UserMobileOtps{UserID: user.ID, MobileNumber: "XXXXXYYYYY", Email: user.Email, OtpCode: "XXXYYY", ExpiredAt: otpExpiration}).Return(someError).Times(1)
mockSmsService.EXPECT().SendOTP(ctx, "XXXXXYYYYY", "XXXYYY").Return(someError).Times(1)
err := uc.SendVerificationOTP(ctx, &user, "XXXXXYYYYY")
assert.Error(t, err)
assert.Equal(t, someError, err)
}
Now on executing test cases, It does not throw any error for RemoveOTP , but gives for rest three methods. Here is the exact error
TestSendUserOTP_Error
controller.go:310: missing call(s) to *mocks.MockTokenService.GetOneTimePassCode() /project_path/user/usecase/user_usecase_test.go
controller.go:310: missing call(s) to *mocks.MockUserRepository.SaveOTP(is equal to context.Background (*context.emptyCtx), is equal to {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} 0 4 XXXXXYYYYY XXXYYY 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {2023-10-06 10:34:25.951558924 +0530 IST m=+600.001514851 true} {0001-01-01 00:00:00 +0000 UTC false}} /project_path/user/usecase/user_usecase_test.go
controller.go:310: missing call(s) to *mocks.MockSMSService.SendOTP(is equal to context.Background (*context.emptyCtx), is equal to XXXXXYYYYY (string), is equal to XXXYYY (string)) /project_path/user/usecase/user_usecase_test.go
controller.go:310: aborting test due to missing call(s)
--- FAIL: TestSendUserOTP_Error (0.00s)
I don't want to use solutions like AnyTimes(),because i am sure all sub methods will be called once. What should be the ideal fix for this? Please suggest.
The problem is your RemoveOTP() function return error so your code does not execute any other methods. (Missing call as the name implied)
Giving AnyTimes() make it run successfully since it can be run 0 times to multiple times.
If you really want to test that all methods are executed once, maybe you can mock all functions to not return error so that it will execute all your codes