Go mockgen模拟函数已调用但测试报‘missing call’问题求助
问题排查:GoMock 期望调用未触发导致测试失败
环境信息
- 系统:Windows 10 Pro(8核处理器)
- Go版本:1.19
- 工具:mockgen
问题现象
使用mockgen生成了Validator接口的Mock实现,调试时确认已通过EXPECT()记录了Mock方法的期望调用,但实际执行测试时,因missing call报错失败。
项目目录结构
cmd configure.go configure_test.go mocks mock_validator.go validator validator.go user user.go go.mod main.go
代码内容
main.go
package main import ( "localdev/mockexample/cmd" ) func main() { cmd.Configure() }
cmd/configure.go
package cmd import ( "fmt" "localdev/mockexample/user" "os" "localdev/mockexample/validator" ) var ( name, password string ) func Configure() { name := os.Args[1] password := os.Args[2] user, err := validate(validator.NewValidator(name, password)) if err != nil { fmt.Printf("%v\n", err) return } fmt.Printf("Credentials are valid. Welcome: %s %s\n", user.FirstName, user.LastName) } func validate(validator validator.Validator) (*user.Data, error) { user, err := validator.ValidateUser() if err != nil { return nil, fmt.Errorf("some thing went wrong. %v", err) } return user, nil }
validator/validator.go
package validator import ( "fmt" "localdev/mockexample/user" ) //go:generate mockgen -destination=../mocks/mock_validator.go -package=mocks localdev/mockexample/validator Validator type Validator interface { ValidateUser() (*user.Data, error) } type ValidationRequest struct { Command string Name string Password string } func (vr ValidationRequest) ValidateUser() (*user.Data, error) { if vr.Name == "bob" && vr.Password == "1234" { return &user.Data{UserID: "123", UserName: "bsmith", FirstName: "Bob", LastName: "Smith"}, nil } return nil, fmt.Errorf("invalid credentials") } func NewValidator(name string, password string) Validator { return &ValidationRequest{Name: name, Password: password} }
user/user.go
package user type Data struct { UserID string `json:"user_id"` UserName string `json:"user_name"` FirstName string `json:"first_name"` LastName string `json:"last_name"` }
cmd/configure_test.go
package cmd import ( "localdev/mockexample/mocks" "localdev/mockexample/user" "os" "testing" "github.com/golang/mock/gomock" ) func TestConfigure(t *testing.T) { t.Run("ConfigureWithMock", func(t *testing.T) { os.Args[1] = "bob" os.Args[2] = "1234" ctrl := gomock.NewController(t) mockValidator := mocks.NewMockValidator(ctrl) //mockValidator.EXPECT().ValidateUser().AnyTimes() // zero more calls, so this will also pass. userData := user.Data{UserID: "testId"} mockValidator.EXPECT().ValidateUser().Return(&userData, nil).Times(1) //(gomock.Any(), gomock.Any()) //(&userData, nil) Configure() }) }
生成的Mock代码(mocks/mock_validator.go)
// Code generated by MockGen. DO NOT EDIT. // Source: localdev/mockexample/validator (interfaces: Validator) // Package mocks is a generated GoMock package. package mocks import ( user "localdev/mockexample/user" reflect "reflect" gomock "github.com/golang/mock/gomock" ) // MockValidator is a mock of Validator interface. type MockValidator struct { ctrl *gomock.Controller recorder *MockValidatorMockRecorder } // MockValidatorMockRecorder is the mock recorder for MockValidator. type MockValidatorMockRecorder struct { mock *MockValidator } // NewMockValidator creates a new mock instance. func NewMockValidator(ctrl *gomock.Controller) *MockValidator { mock := &MockValidator{ctrl: ctrl} mock.recorder = &MockValidatorMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockValidator) EXPECT() *MockValidatorMockRecorder { return m.recorder } // ValidateUser mocks base method. func (m *MockValidator) ValidateUser() (*user.Data, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ValidateUser") ret0, _ := ret[0].(*user.Data) ret1, _ := ret[1].(error) return ret0, ret1 } // ValidateUser indicates an expected call of ValidateUser. func (mr *MockValidatorMockRecorder) ValidateUser() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateUser", reflect.TypeOf((*MockValidator)(nil).ValidateUser)) }
问题根源
测试代码中虽然创建了MockValidator实例并设置了期望调用,但**Configure()函数内部始终调用validator.NewValidator()创建真实的ValidationRequest对象,完全没有使用到你创建的Mock实例**。这就是为什么Mock的期望调用从未被触发,最终导致missing call错误。
修复方案
需要通过依赖注入让Configure()可以接收外部传入的Validator实例,这样测试时就能传入Mock对象,而不是每次都创建真实实现。
修改后的cmd/configure.go
package cmd import ( "fmt" "localdev/mockexample/user" "os" "localdev/mockexample/validator" ) var ( name, password string ) // 新增可注入Validator的版本,供测试使用 func ConfigureWithValidator(v validator.Validator) { user, err := validate(v) if err != nil { fmt.Printf("%v\n", err) return } fmt.Printf("Credentials are valid. Welcome: %s %s\n", user.FirstName, user.LastName) } // 原Configure函数保持不变,供主程序使用 func Configure() { name := os.Args[1] password := os.Args[2] ConfigureWithValidator(validator.NewValidator(name, password)) } func validate(validator validator.Validator) (*user.Data, error) { user, err := validator.ValidateUser() if err != nil { return nil, fmt.Errorf("something went wrong. %v", err) } return user, nil }
修改后的cmd/configure_test.go
package cmd import ( "localdev/mockexample/mocks" "localdev/mockexample/user" "testing" "github.com/golang/mock/gomock" ) func TestConfigure(t *testing.T) { t.Run("ConfigureWithMock", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() // 确保控制器在测试结束时检查所有期望调用 mockValidator := mocks.NewMockValidator(ctrl) userData := user.Data{UserID: "testId", FirstName: "Test", LastName: "User"} mockValidator.EXPECT().ValidateUser().Return(&userData, nil).Times(1) // 调用新增的ConfigureWithValidator,传入Mock实例 ConfigureWithValidator(mockValidator) }) }
额外优化建议
- 在测试中使用
defer ctrl.Finish(),确保控制器在测试结束时自动验证所有期望调用是否完成。 - 避免直接修改
os.Args,如果需要测试命令行参数,可以通过参数传递或者使用模拟库,本次修复已不再需要修改os.Args。
内容的提问来源于stack exchange,提问作者ZiimyX
相关产品推荐
相关产品推荐

