在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中执行此操作的最佳方法是什么?或者我错过了一些更基本的东西?
答案 0 :(得分:4)
如果将接口类型本身嵌入模拟结构中,则只能实现所需的方法。例如:
type MockCar struct {
Car
...
}
func (c MockCar) changeTire() {...}
即使你的struct只显式地实现changeTire
,它仍然满足接口,因为Car
字段提供了其余的。只要您不尝试调用任何未实现的方法(这会导致恐慌,因为Car
为nil
)
答案 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方法中。这样就可以实现两全其美,默认的非平凡实现和只需更改相关的函数字段即可随意热交换模拟实现。