在Go中模拟单个方法

时间:2017-03-10 18:00:36

标签: testing go mocking

在Go中,如何在不必实现每个方法的情况下模拟接口?我们假设我有一个Car接口和一个实现该接口的Corolla结构:

type Car interface {
    changeTire()
    startEngine()
    ....
    refuel()
}

type Corolla struct {
    ...
}

func (c Corolla) changeTire() {...}

func (c Corolla) startEngine() {...}

func (c Corolla) refuel() {...}

我们说我的Garage结构取决于Car

type Garage struct {
    MyCar Car
}

func (g Garage) PrepareCarToDrive() {
    g.MyCar.changeTire()
    g.MyCar.refuel()
    g.MyCar.startEngine()
}

我想测试Garage,因此我创建了MockCar来实现Car

type MockCar struct {
    ...
}

func (c MockCar) changeTire() {...}

func (c MockCar) startEngine() {...}

func (c MockCar) refuel() {...}

现在我有测试PrepareCarToDrive并使用MockCar

的测试
func TestGarage_PrepareCarToDrive_DoesSomething(t *testing.T) {
    mockCar := MockCar{}
    garageUnderTest := Garage{}
    garageUnderTest.MyCar = mockCar

    // some other setup

    // when Garage calls mockCar.changeTire(), should do X
    ...
}

func TestGarage_PrepareCarToDrive_DoesSomethingElse(t *testing.T) {
    mockCar := MockCar{}
    garageUnderTest := Garage{}
    garageUnderTest.MyCar = mockCar

    // some other setup 
    // when Garage calls mockCar.changeTire(), should do Y
    ...
}

我的问题是,我怎样才能mockCar每次测试做不同的事情?我知道我可以为每个测试创建Car的不同模拟实现。但是当我向Car添加更多方法时,这将很快失控。

我来自Java背景,所以我正在寻找像Mockito这样的东西,让我模拟每个测试所需的方法。

在Go中执行此操作的最佳方法是什么?或者我错过了一些更基本的东西?

2 个答案:

答案 0 :(得分:4)

如果将接口类型本身嵌入模拟结构中,则只能实现所需的方法。例如:

type MockCar struct {
    Car
    ...
}

func (c MockCar) changeTire() {...}

即使你的struct只显式地实现changeTire,它仍然满足接口,因为Car字段提供了其余的。只要您不尝试调用任何未实现的方法(这会导致恐慌,因为Carnil

答案 1 :(得分:1)

最简单的方法是使用一些基本实现作为测试结构的嵌入,并且只覆盖您正在测试的方法。使用您的类型的示例:

type MockCar struct {
    Corolla // embedded, so the method implementations of Corolla get promoted
}

// overrides the Corolla implementation
func (c MockCar) changeTire() {
    // test stuff
}

// refuel() and startEngine(), since they are not overridden, use Corolla's implementation

https://play.golang.org/p/q3_L1jf4hk

另一种方法是,如果每个测试需要不同的实现,则使用带有函数字段的模拟:

type MockCar struct {
    changeTireFunc func()
    startEngineFunc func()
    ....
    refuelFunc func()
}

func (c MockCar) changeTire() {
    if c.changeTireFunc != nil {
        c.changeTireFunc()
    }
}

func (c MockCar) startEngine() {
    if c.startEngineFunc != nil {
        c.startEngineFunc()
    }
}

func (c MockCar) refuel() {
    if c.refuelFunc != nil {
        c.refuelFunc()
    }
}

// test code

func TestGarage_PrepareCarToDrive_DoesSomething(t *testing.T) {
    // let's say we require refuel(), but the default implementation is fine
    // changeTire(), however, requires a mocked testing implementation
    // and we don't need startEngine() at all
    mockCar := MockCar{
        changeTireFunc: func() {
            // test functionality
        },
        refuelFunc: Corolla.refuel,
    }
    garageUnderTest := Garage{}
    garageUnderTest.MyCar = mockCar

    // some other setup

    // when Garage calls mockCar.changeTire(), should do X
    ...
}

https://play.golang.org/p/lf7ny-lUCS

当您尝试使用其他类型的方法作为默认实现时,此样式不太有用,但如果您有一些可用作默认或测试实现的独立函数,则该样式非常有用,或者如果你没有特别嘲笑的函数可以接受一个微不足道的返回(比如上面例子中被模拟的startEngine()的行为,因为startEngineFunc字段为nil,所以在调用时什么都不做)。

如果相关的函数字段为nil,您也可以根据需要将默认实现(如对(Corolla{}).startEngine()的调用)烘焙到mock方法中。这样就可以实现两全其美,默认的非平凡实现只需更改相关的函数字段即可随意热交换模拟实现。