You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
    })
}

额外优化建议

  1. 在测试中使用defer ctrl.Finish(),确保控制器在测试结束时自动验证所有期望调用是否完成。
  2. 避免直接修改os.Args,如果需要测试命令行参数,可以通过参数传递或者使用模拟库,本次修复已不再需要修改os.Args。

内容的提问来源于stack exchange,提问作者ZiimyX

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 19:55:18