不久前,我接到了一个需求:开发同学希望能对 RPC 调用进行 mock。但问题在于,client 并没有被抽象成接口,因此无法直接使用传统的 mock 技术。为了实现这个目标,我不得不在 client 的方法内部嵌入 mock 能力。最终实现效果如下所示:

type Client struct{}

// 新增代码:获取 Client 的运行时类型
var clientType = reflect.TypeFor[Client]()

func (c *Client) Get(ctx context.Context, req *Request) (*Response, error) {
// 新增代码:通过 ctx 获取 mock 信息;若成功 mock,则解析返回值
if ret, ok := mock.InvokeContext(ctx, clientType, "Get", ctx, req); ok {
return mock.Unbox2[*Response, error](ret)
 }
// 原有逻辑...
}

// 新增代码:返回 mock 配置对象,可设置被 mock 方法的行为
func MockGet(r *mock.Manager) *mock.Mocker22[context.Context, *Request, *Response, error] {
return mock.NewMocker22[context.Context, *Request, *Response, error](r, clientType, "Get")
}

可以看到,为了引入 mock 能力,只需要新增三段代码即可。使用该 mock 能力写测试时的代码也非常简洁:

r := mock.NewManager()
ctx := r.BindTo(t.Context())

MockGet(r).Handle(func(ctx context.Context, req *Request) (resp *Response, err error) {
 return &Response{}, nil
})

var c Client
resp, err := c.Get(ctx, &Request{})
assert.Nil(t, err)

是不是看起来很简单?只需要将 mock 配置绑定到 ctx 上,mock 就能自动生效,使用起来非常顺滑。

为什么不使用 monkey patch 方案?

其实最初研发同学使用的就是 monkey 方式进行 mock 的。但是 monkey 的 API 缺乏类型提示,导致 IDE 无法提供自动补全,影响开发效率。而我现在这套方案则完全避免了这个问题,借助 Go 1.18+ 的泛型能力,实现了类型安全的 mock,配合 IDE 自动补全,用起来如丝般顺滑。

所以相较于目前市面上的主流 mock 工具,我这个方案不仅使用简单,还天然具备类型安全优势。

事情到此为止了吗?😏

当然没有。

在优化 mock 方案的过程中,我逐步意识到,这套机制不仅可以满足当前的需求,甚至有潜力完全替代 Go 官方的 mockgen 工具。为什么这么说?

官方 mockgen 存在几个问题:

  1. 使用繁琐,每个测试都要显式创建 controller 并手动调用 defer Finish()
  2. 参数匹配不友好,尤其在处理复杂参数结构或仅需匹配部分参数的场景下;
  3. 缺乏类型提示,IDE 补全无效;
  4. 基于录制-回放-校验的工作模式,但很多场景我们只需要一个 stub。

示例代码如下(出自官方 mock):

mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

mockDoer := mocks.NewMockDoer(mockCtrl)
testUser := &user.User{Doer: mockDoer}

mockDoer.EXPECT().DoSomething(123, "Hello GoMock").Return(nil).Times(1)

testUser.Use()

其实,在很多测试中我们更关心“替代逻辑”,而不是“调用校验”。因此我最终实现了一个更加灵活、现代化的 mockgen 工具 —— gsmock

👉 https://github.com/go-spring/mock

gsmock 的核心亮点:

1. 基于 context.Context 传递 mock 信息

这在 无法抽象为接口的场景 下尤其重要。只要结合自定义代码生成工具,即可将 mock 嵌入结构体方法中,且不会污染业务逻辑。

2. 类型安全,IDE 支持完美

借助 Go 泛型机制,为每个 mock 函数提供完整的类型提示与参数校验,无需反复查文档,IDE 补全一应俱全

3. 多种 mock 模式:when/return 与 handle 灵活组合

你可以像写 switch-case 一样进行 mock:

when: // 匹配条件
 return // 返回结果
when:
 return
handle // 默认处理逻辑

无需显式声明“期望被调用几次”,更贴近实际测试需求。

4. 自动重置 mock 状态

对于存在多种测试场景的函数,mock 行为可能固定,但测试逻辑各不相同。这时只需要 reset 一下,即可复用已有配置,无需重复构造。

结语

总的来说,gsmock 在保留灵活性和强大功能的同时,提供了更简洁、更现代化的 mock 使用体验。欢迎大家查阅 项目源码和README,希望这个工具能成为你在 Go 单元测试中的得力助手!

----------------------------------------------------

欢迎关注 Go-Spring 项目!

GitHub 地址:https://github.com/go-spring/spring-core

欢迎扫码关注微信公众号 GoSpring实战,获取更多实战分享。

欢迎扫码加入 Go-Spring 讨论群,与开发者们共同探讨技术,交流经验。

您的支持对 Go-Spring 非常重要!欢迎点赞在看分享,助力 Go-Spring 的成长与发展!

 posted on 2025-06-24 15:34  lvan100  阅读(23)  评论(0)    收藏  举报