mock失败的根本原因是类型未对齐:go不支持跨包结构体指针互转,必须让真实实现与mock共同实现同一接口,且函数参数声明为该接口类型而非具体结构体指针。

Mock失败的根本原因:不是没写对,是类型没对齐
Go里Mock报错 cannot use *MockX as type *realpkg.Y,90%是因为你把指针类型当接口用了。Go不支持跨包结构体指针互转,哪怕字段一模一样也不行。真实存储和Mock必须实现同一个接口,而函数参数必须声明为该接口类型,不是具体结构体指针。
常见错误写法:func Handle(s *datastore.Storage) → 传&MockStore{}必然编译失败。
- 正确做法:定义
type Store interface{ Get(...); Set(...) } - 让
datastore.Storage(或其包装器)实现该接口 - 让
MockStore也实现该接口,且方法接收者用*MockStore(否则调用时会丢失状态) - 业务函数签名改为
func Handle(s Store)——注意这里不是Store指针,而是接口值本身
文件系统Mock:别碰os.ReadFile,改用io/fs.FS
直接在测试里调os.ReadFile再配t.TempDir(),等于没Mock——它还是在读磁盘,只是路径临时而已。并发时容易冲突,CI上偶发失败,性能也差。
Go 1.16+ 的标准解法是依赖io/fs.FS接口:
- 被测函数接收
fs.FS参数,而不是硬编码调os.ReadFile - 生产环境用
os.DirFS("/etc"),测试用afero.NewMemMapFs() - 预置测试文件只需一行:
afero.WriteFile(fs, "config.json", []byte(`{}`), 0644) - 避免手写MockFS,除非你明确需要控制
Stat返回的fs.FileInfo细节
gomock工具链现在该用哪个?
官方github.com/golang/mock已归档,当前主流是go.uber.org/mock。但要注意:它生成的是强约束Mock,适合大型项目;小项目或快速验证,手写Mock更轻量、更可控。
- 安装:
go install go.uber.org/mock/mockgen@latest - 生成命令示例:
mockgen -source=store.go -destination=mock_store.go -package=mock - 生成后必须调用
ctrl.Finish(),否则未预期调用会panic - 如果接口方法少(比如就2~3个),直接手写
type MockStore struct{ ... }+ 实现方法,省去生成步骤和依赖
时间、HTTP客户端等“全局依赖”怎么Mock?
这类依赖不能靠接口注入?其实可以,关键是把它变成可传入的参数。比如时间获取,不要写time.Now(),而是定义type Clock interface{ Now() time.Time },默认用realClock{},测试时传fixedClock{t: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)}。
HTTP客户端同理:
- 不要在函数里直接用
http.DefaultClient - 定义
type HTTPDoer interface{ Do(*http.Request) (*http.Response, error) } - 生产传
http.DefaultClient,测试传&http.Client{Transport: &mockRoundTripper{}} - mockRoundTripper可简单返回预设
*http.Response,无需启动真实服务
所有这些Mock方式,核心就一条:把“谁做事”从代码里抽出来,变成能换的输入。不是为了Mock而Mock,而是为了让逻辑可验证、可隔离、可预测。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











