【问题标题】:How do I mock a function from another package without using dependency injection?如何在不使用依赖注入的情况下模拟另一个包中的函数?
【发布时间】:2020-02-03 21:38:20
【问题描述】:

有点像 golang 初学者,但我以前使用过测试框架。如何在不注入依赖项的情况下模拟和伪造依赖方法返回的内容?我不想使用依赖注入的原因是因为使用了许多外部包方法,并且在构造函数中注入所有方法很笨拙。

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

这是我在代码方面尝试做的事情:

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"
}

谢谢!

【问题讨论】:

  • 如果你想使用模拟,你的选择几乎是 a) 依赖注入,或 b) 服务定位器。使用模拟的代码必须从外部某个地方获取模拟,这可以决定它是获取模拟还是真正的实现。
  • 你是否需要编写一个包 b/b.go 的测试用例,它具有 a/a.go 的实现,你需要使用接口伪造你的 a/a.go 调用

标签: unit-testing testing go


【解决方案1】:

您可以使用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

【讨论】:

  • 好主意,但测试在包 b 中,所以这可能是 mock / build 标签应该在的地方。另外,我认为必须添加标志go test 才能获得正确的测试。
【解决方案2】:

这样做的一种方法是使用您要调用的函数创建一个变量,因此在 b/b.go 中包含以下内容:

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

现在在 b_test.go 你可以这样做:

func TestPrintResults(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)            
}

【讨论】:

  • 我会说这是一种规范的方式。它可以防止 Parallel() 测试。如果您来自沉重的 OO 背景,它看起来非常臭。但就可读性而言,它是最佳的。
【解决方案3】:

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

您可以尝试将包名称别名为实现与外部包功能匹配的接口的全局变量。这具有不需要在使用包“a”的情况下进行内联更改的优点。

这个想法围绕着为您从外部包中需要的功能创建一个接口,该接口的默认行为的传递实现和测试的模拟实现。在测试开始时,只需将全局变量替换为 mock。

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)            
}

这有点偷偷摸摸,所以你可能想清楚地知道发生了什么。


【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-10-18
    • 1970-01-01
    • 2023-04-09
    • 2020-12-08
    • 1970-01-01
    • 2019-06-08
    • 1970-01-01
    • 2013-02-19
    相关资源
    最近更新 更多