You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Go单元测试中Mock嵌套结构体的方法?

解决方案

由于你无法修改原有结构体和方法的代码,而OuterThing的innerThing字段是具体类型*InnerThing而非接口,常规的接口依赖注入方式不适用。以下两种方法可以实现Mock需求:

方法1:使用方法Patch库(推荐)

借助go-mpatch这类库可以直接替换目标方法的实现,无需改动原代码,是最稳妥的方案。

步骤:

  1. 安装依赖:
go get github.com/undefinedlabs/go-mpatch
  1. 编写测试用例:
import (
    "errors"
    "testing"

    "github.com/undefinedlabs/go-mpatch"
)

func TestDoLotsOfStuff(t *testing.T) {
    testCases := []struct {
        name        string
        mockHandler func(*InnerThing, string) (uint64, error)
        expectErr   bool
    }{
        {
            name: "happy case",
            mockHandler: func(_ *InnerThing, _ string) (uint64, error) {
                return 123, nil
            },
            expectErr: false,
        },
        {
            name: "error case",
            mockHandler: func(_ *InnerThing, _ string) (uint64, error) {
                return 0, errors.New("mock error")
            },
            expectErr: true,
        },
    }

    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            // Patch InnerThing.DoStuff方法,替换为mock逻辑
            patch, err := mpatch.PatchMethod((*InnerThing).DoStuff, tc.mockHandler)
            if err != nil {
                t.Fatalf("patch failed: %v", err)
            }
            defer patch.Unpatch() // 测试结束后恢复原方法

            // 初始化测试对象
            outer := &OuterThing{
                innerThing: &InnerThing{name: "test"},
            }

            outer.doLotsOfStuff()

            // 这里添加你的断言逻辑,比如验证错误分支是否执行
        })
    }
}

方法2:使用unsafe包(不推荐)

利用unsafe包转换指针类型,将Mock实例伪装成*InnerThing。这种方式依赖Go的内存布局,存在版本兼容性风险,仅适合临时测试场景。

步骤:

  1. 定义Mock结构体(借助testify/mock简化Mock逻辑):
import "github.com/stretchr/testify/mock"

type MockInnerThing struct {
    mock.Mock
}

func (m *MockInnerThing) DoStuff(x string) (uint64, error) {
    args := m.Called(x)
    return args.Get(0).(uint64), args.Error(1)
}
  1. 编写测试用例:
import (
    "errors"
    "testing"
    "unsafe"

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

func TestDoLotsOfStuff_Unsafe(t *testing.T) {
    t.Run("error case", func(t *testing.T) {
        // 初始化Mock并设置预期
        mockInner := new(MockInnerThing)
        mockInner.On("DoStuff", "lots of stuff").Return(uint64(0), errors.New("mock error"))

        // 用unsafe转换指针类型,将Mock实例赋值给innerThing
        outer := &OuterThing{
            innerThing: (*InnerThing)(unsafe.Pointer(mockInner)),
        }

        outer.doLotsOfStuff()

        // 验证Mock的预期是否被调用
        mockInner.AssertExpectations(t)
        // 添加其他业务断言
    })
}

关于场景的说明

这种需求并不罕见,Go的设计理念更偏向依赖接口而非具体类型,所以在业务代码初期定义接口能大幅降低Mock难度。但面对无法修改的遗留代码,方法Patch类库是更可靠的选择。

内容的提问来源于stack exchange,提问作者kiltek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 16:40:26