Fork me on GitHub

1.定义

gomock是一个用于Go语言的Mock框架,
它可以帮助开发者在单元测试中模拟(Mock)接口的行为,
从而隔离被测试代码与外部依赖,使测试更加独立、稳定和高效。

项目原地址为 golang/mock
2023年6月之后,代码库已经归档,官方推荐使用go.uber.org/mock

2.安装

go install go.uber.org/mock/mockgen@latest

3.生成Mock代码

有文件itf.go

mockgen -source=./itf.go -destination=./mock.go -package=mymock
  • -source:指定包含接口定义的源文件。
  • -destination:用于写入结果源代码的文件。
  • -package:指定生成的Mock代码所在的包名。

4.单元测试

预设的请求,可以按照顺序调用或非顺序调用

4.1 非顺序调用

import (
    "fmt"
    "go.uber.org/mock/gomock"
    "testing"
)

func TestFoo(t *testing.T) {
    // 创建Mock控制器
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    m := NewMockFoo(ctrl)
    m.EXPECT().Bar(1).Return(1)
    m.EXPECT().Bar(2).Return(2)

    fmt.Println("--1--", m.Bar(2))
    fmt.Println("--2--", m.Bar(1))
}

output:


 --1-- 2
 --2-- 1

可以按照任意顺序调用Bar()

4.2 顺序调用

func TestFoo2(t *testing.T) {
    // 创建Mock控制器
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    m := NewMockFoo(ctrl)
    gomock.InOrder( // 必须按照指定顺序调用Bar()
        m.EXPECT().Bar(1).Return(1),
        m.EXPECT().Bar(2).Return(2),
    )

    fmt.Println("--1--", m.Bar(2))
    fmt.Println("--2--", m.Bar(1))
}

单元测试运行错误

=== RUN   TestFoo2
    mymock_test.go:33: Unexpected call to *mymock.MockFoo.Bar([2]) at  mymock/mymock_test.go:33 because: 
        expected call at  mymock/mymock_test.go:29 doesn't match the argument at index 0.
        Got: 2 (int)
        Want: is equal to 1 (int)
        expected call at  mymock/mymock_test.go:30 doesn't have a prerequisite call satisfied:
        *mymock.MockFoo.Bar(is equal to 1 (int))  mymock/mymock_test.go:29
        should be called before:
        *mymock.MockFoo.Bar(is equal to 2 (int))  mymock/mymock_test.go:30
    controller.go:251: missing call(s) to *mymock.MockFoo.Bar(is equal to 1 (int))  mymock/mymock_test.go:29
    controller.go:251: missing call(s) to *mymock.MockFoo.Bar(is equal to 2 (int))  mymock/mymock_test.go:30
    controller.go:251: aborting test due to missing call(s)
--- FAIL: TestFoo2 (0.00s)

4.3 按照指定函数输出返回值

func TestFoo3(t *testing.T) {
    // 创建Mock控制器
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    m := NewMockFoo(ctrl)
    m.EXPECT().Bar(1).DoAndReturn(func(x int) int {
        return x + 10
    })
    m.EXPECT().Bar(2).DoAndReturn(func(x int) int {
        return x + 10
    })

    fmt.Println("--1--", m.Bar(1))
    fmt.Println("--2--", m.Bar(2))
}

output:


--1-- 11
--2-- 12

4.4 命中call,触发hook操作

func TestFoo4(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    m := NewMockFoo(ctrl)
    // Do中传入的函数相当于Hook函数
    m.EXPECT().Bar(1).Do(func(x int) int {
        fmt.Println("x: ", x)
        return -1 // 返回值无意义
    }).Return(10)

    fmt.Println("--1--", m.Bar(1))
}

output:

x:  1
--1-- 10

4.5 其它常用函数

func TestFoo5(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    m := NewMockFoo(ctrl)
    m.EXPECT().Bar(1).Return(1)
    // gomock.Any() 表示匹配任意参数
    // AnyTimes() 表示匹配任意多次
    m.EXPECT().Bar(gomock.Any()).Return(100).AnyTimes()

    fmt.Println("--1--", m.Bar(1))
    fmt.Println("--2--", m.Bar(2))
    fmt.Println("--3--", m.Bar(3))
    fmt.Println("--4--", m.Bar(4))
}

output:


--1-- 1
--2-- 100
--3-- 100
--4-- 100   

4.6 使用自定义的匹配器

gomock默认使用eqMatcher来判断请求参数列表预设的参数列表是否相同

eqMatcher.Matches() -> reflect.DeepEqual()

matchers.go

type eqMatcher struct {
    x any
}

func (e eqMatcher) Matches(x any) bool {
    // In case, some value is nil
    if e.x == nil || x == nil {
        return reflect.DeepEqual(e.x, x)
    }

    // Check if types assignable and convert them to common type
    x1Val := reflect.ValueOf(e.x)
    x2Val := reflect.ValueOf(x)

    if x1Val.Type().AssignableTo(x2Val.Type()) {
        x1ValConverted := x1Val.Convert(x2Val.Type())
        return reflect.DeepEqual(x1ValConverted.Interface(), x2Val.Interface())
    }

    return false
}

我们可以自定义自己的Matcher, 满足Matcher接口即可

type Matcher interface {
    // Matches returns whether x is a match.
    Matches(x any) bool

    // String describes what the matcher matches.
    String() string
}

示例


type CustomMatcher struct {
    Value *Car
}

func NewCustomMatcher(value *Car) *CustomMatcher {
    return &CustomMatcher{Value: value}
}

func (m *CustomMatcher) Matches(x interface{}) bool {
    value2 := x.(*Car)
    fmt.Println("value1:", m.Value, "value2:", value2)
    return m.Value.Age == value2.Age
}

func (m *CustomMatcher) String() string {
    return fmt.Sprintf("CustomMatcher(%v)", m.Value)
}

func TestDealer(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    m := NewMockDealer(ctrl)
    m.EXPECT().Evaluate(NewCustomMatcher(&Car{Color: "red", Age: 1})).Return(100).AnyTimes()

    fmt.Println("--1--", m.Evaluate(&Car{Color: "red", Age: 1}))
    fmt.Println("--2--", m.Evaluate(&Car{Color: "blue", Age: 1})) // 都会命中预设的Call
}

output:


value1: &{1 red} value2: &{1 red}
--1-- 100
value1: &{1 red} value2: &{1 blue}
--2-- 100

完整代码见
mygomock

5.总结

gomock的功能非常强大,配合gomonkey使用,足以应对日常的单元测试场景。

参考资料

1.Golang 中 mock 库的实现

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注