mock对象的核心作用是隔离被测代码与外部依赖,让测试只关注逻辑本身;通过接口定义依赖、手动编写轻量mock或用mockery自动生成mock来实现,需避免mock标准库、过度mock及接口粒度不合理等问题。
在 Go 单元测试中,mock 对象的核心作用是**隔离被测代码与外部依赖(如数据库、HTTP 客户端、第三方服务)**,让测试只关注逻辑本身。Go 本身不提供内置 mock 框架,但通过接口 + 组合 + 手动实现或工具生成 mock,可以高效完成依赖行为模拟。
Go 的接口天然支持鸭子类型,这是 mock 的基础。关键不是“继承”,而是“实现同一接口”。例如:
type PaymentService interface {
Charge(amount float64, cardToken string) (string, error)
}
// 真实实现(生产环境用)
type StripePayment struct{}
func (s StripePayment) Charge(amount float64, cardToken string) (string, error) {
// 调用 Stripe API
}
// 被测业务逻辑依赖接口,而非具体类型
type OrderProcessor struct {
payment PaymentService // 依赖注入接口
}
func (p *OrderProcessor) Process(order Order) error {
_, err := p.payment.Charge(order.Total, order.CardToken)
return err
}
这样,测试时只需传入一个满足 PaymentService 接口的 mock 实例,无需改动业务逻辑代码。
对小接口或少量方法,直接写结构体+方法即可,清晰可控、无额外依赖:
mock,设置预期行为,再注入到被测对象示例:
type MockPaymentService struct {
ReturnID string
ReturnErr error
Called int
}
func (m *MockPaymentService) Charge(amount float64, cardToken string) (string, error) {
m.Called++
return m.ReturnID, m.ReturnErr
}
func TestOrderProcessor_Process_Success(t *testing.T) {
mock := &MockPaymentService{
ReturnID: "pay_123",
ReturnErr: nil,
}
proc := &OrderProcessor{payment: mock}
err := proc.Process(Order{Total: 99.99, CardToken: "tok_visa"})
if err != nil {
t.Fatal(err)
}
if mock.Called != 1 {
t.Error("expected Charge to be called once")
}
}
当接口多、方法复杂时,手动 mock 易出错且维护成本高。推荐使用 mockery 工具自动生成:
go install github.com/vektra/mockery/v2@latest
mockery --name=PaymentService → 生成 mocks/MockPaymentService.go
On("Charge", 100.0, "tok").Return("pay_xxx", nil))、断言调用次数、校验参数等testify/mock 或原生 testing 使用,提升可读性注意:mockery 生成的是“桩”(stub),若需验证交互(如“是否以特定参数调用了某方法”),仍需在测试中检查 AssertExpectations 或手动计数。
不要 mock 你没拥有的东西:只 mock 自己定义的接口,不 mock 标准库(如 net/http.Client)或第三方 struct——应封装成接口再 mock。
不要过度 mock:仅 mock 真正影响测试稳定性的外部依赖;内存缓存、纯计算逻辑等无需 mock。
保持 mock 行为贴近真实约束:比如真实接口可能对空 cardToken 返回 error,mock 也应覆盖该分支,否则测试通过但线上失败。
接口粒度要合理:一个接口含 10 个方法?很可能违反单一职责,拆分后更易 mock 和复用。