如何在不使用依赖注入的情况下从另一个包中模拟一个函数?

时间:2018-07-19 17:24:15

标签: unit-testing testing go

有点像golang的初学者,但是我之前已经使用过测试框架。我该如何模拟并伪造依赖方法返回的结果而不注入依赖?我不想使用依赖项注入的原因是因为有许多外部包方法正在使用,并且将所有方法注入构造函数中都很难。

我已经搜索了此在线/堆栈溢出,解决方案是始终使用依赖项注入。有时这不是一个可行的选择。

这就是我要在代码方面做的事情:

b / b_test.go

package b

func TestResults(t *testing.T) {
    t.Run("Test", func(t *testing.T) {
        b := NewB()
        // How do I mock out and fake a.DoSomething() to be
        // "complete" instead of whats in the code right now?
        result = b.Results()
        assert.Equal(t, "complete", result)            
    }
}

b / b.go

package b

import "a"

type B struct {}

func NewB() B {
    return &B{}
}

func (b B) Results() {
    return a.DoSomething()
}

a / a.go

package a

func DoSomething() {
    return "done"
}

谢谢!

3 个答案:

答案 0 :(得分:5)

您可以使用conditional compilation with build tags

a / a.go

// +build !mock

package a
func DoSomething() {
    return "done"
}

a / a_mock.go

// +build mock

package a
func DoSomething() {  // Insert fake implementation here
    return "complete"
}

$ go test -tags mock

答案 1 :(得分:3)

一种方法是使用您要调用的函数创建一个变量,因此在b.go中添加以下内容:

doSomething := func() { a.DoSomething() }
func (b B) Results() {
    return a.DoSomething()
}

现在在b_test.go中,您可以执行以下操作:

func TestPrintResults(t *testing.T) {
    t.Run("Test", func(t *testing.T) {
        origDoSomething := doSomething
        defer func() { doSomething = origDoSomething }
        doSomething = func() {
          // Insert fake implementation here
        }
        b := NewB()
        result = b.Results()
        assert.Equal(t, "complete", result)            
    }
}

答案 2 :(得分:3)

我不确定我是否不理解您对依赖关系注入的反对,但是可以使用接口使依赖关系注入相对轻松。特别是现有代码无需修改

您可以尝试将软件包名称别名为一个全局变量,该变量实现与外部软件包功能匹配的接口。这样做的好处是,在使用软件包“ a”的地方不需要进行内联更改。

这个想法围绕着从外部包中为您所需的功能创建接口,针对默认行为的该接口的直通实现以及用于测试的模拟实现。在测试开始时,只需将模拟变量替换为全局变量即可。

b / a_interface.go

package b

import (
    aa "a" // alias the "a" package
)

// global variable that mimics the external package "a"
var a aType

// internal interface for `a` package functions (i.e. `DoSomething()`)
type aDoer interface {
    DoSomething() string
}

// default implementation of the aDoer interface
type aType struct{}

func (aType) DoSomething() string {
    // just pass-through to package "a"
    return aa.DoSomething()
}

b / b.go-未修改,然后删除导入:

package b

type B struct{}

func NewB() B {
    return B{}
}

func (b B) Results() string{
    // now `a` is a global variable not a package.
    return a.DoSomething()  
}

b / b_test.go

package b

import (
    "testing"

    "github.com/stretchr/testify/assert"
)

// mock implementation of aDoer interface
type aMock struct{}

func (aMock) DoSomething() string {
    return "complete"
}

func TestResults(t *testing.T) {
    a = aMock{}  // <- replace the default with the mock

    b := NewB()

    result = b.Results()
    assert.Equal(t, "complete", result)            
}

这有点偷偷摸摸的一面,所以您可能想对发生的事情做出明确的评论。