简体   繁体   English

在Go测试中模拟嵌入式结构

[英]Mock embedded struct in go test

Given: 鉴于:

// FileReader interface for reading from a file
type FileReader interface {
    ReadFile(filename string) ([]byte, error)
}

type FileRead struct {}

// ReadFile reads from filename fn using ioutilReadFile
func (fr FileRead) ReadFile(fn string) ([]byte, error) {
    return ioutil.ReadFile(fn)
}

type Dev struct {
    *FileRead
}

func NewDev() *Dev {
    frd := FileRead{}
    return &Dev{frd}
}

// Function that does some job
func (dev Dev) DoSomeStuff() {
    //...
    dev.ReadFile("file")
    //...
}

func main () {
    doer := NewDev()
    doer.DoSomeStuff()
}

During unit testing, the ReadFile operation should be mocked. 在单元测试期间,应该模拟ReadFile操作。 How can one best achieve this in go test? 如何在测试中最好地做到这一点?

Dev struct could instead use FileReader interface, but then struct embedding is no longer used and the syntax in DoSomeStuff becomes less obvious. 开发结构可以改用FileReader接口,但是结构嵌入不再使用,DoSomeStuff中的语法也不太明显。

If you are using a DI framework such as Dargo you can inject something that implements FileReader into dev. 如果您使用的是诸如Dargo之类的DI框架,则可以将一些实现FileReader的东西注入到dev中。 In your main-line code you would bind the normal FileReader, but in your test you can use a mock FileReader. 在您的主代码中,您将绑定普通的FileReader,但是在测试中,您可以使用模拟FileReader。 The rest of your code should not know the difference. 您的其余代码不应该知道区别。 It would look something like this: 它看起来像这样:

import (
    "github.com/jwells131313/dargo/ioc"
    "io/ioutil"
    "testing"
)

type FileReader interface {
    ReadFile(filename string) ([]byte, error)
}

type FileRead struct{}

// ReadFile reads from filename fn using ioutilReadFile
func (fr FileRead) ReadFile(fn string) ([]byte, error) {
    return ioutil.ReadFile(fn)
}

type Dev struct {
    MyReader FileReader `inject:"FileReader"`
}

/* Not here anymore, but you can implement DargoInitializer
   if you need more initialization of Dev
func NewDev() *Dev {
    frd := FileRead{}
    return &Dev{frd}
}
*/

// Function that does some job
func (dev Dev) DoSomeStuff() {
    //...
    dev.MyReader.ReadFile("file")
    //...
}

var locator ioc.ServiceLocator

func initialize() error {
    l, err := ioc.CreateAndBind("Example", func(binder ioc.Binder) error {
        binder.Bind("Dev", &Dev{})
        binder.Bind("FileReader", &FileRead{})
        return nil
    })

    locator = l

    return err

}

func main() {
    initialize()

    raw, _ := locator.GetDService("Dev")
    doer := raw.(*Dev)

    doer.DoSomeStuff()
}

// Here is test code

type TestFileRead struct{}

// ReadFile is a mock that just returns a zero-length byte array
func (tfr TestFileRead) ReadFile(fn string) ([]byte, error) {
    return []byte{}, nil
}

func TestMe(t *testing.T) {
    initialize()

    ioc.BindIntoLocator(locator, func(binder ioc.Binder) error {
        binder.Bind("FileReader", &TestFileRead{}).Ranked(1)
        return nil
    })

    // Test Me!

}

In the above example the "normal" file reader is injected in the normal code, but in the test there is a TestFileReader with a higher rank. 在上面的示例中,“常规”文件阅读器已注入常规代码中,但是在测试中,具有更高级别的TestFileReader。 So when you went to get Dev in the test it would be injected with the test one, not the one from the main-line code. 因此,当您去测试中获得Dev时,它将被注入一个测试,而不是主代码中的一个。

Hope this helps. 希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM