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

无法Mock utils.CreateConnection报错:非可寻址或映射索引表达式

Go函数测试Mock失败问题解决

问题描述

我有三个互相依赖的Go内部函数,编写测试用例时尝试Mock utils.CreateConnection 和 sendRequest,但因代码耦合问题无法替换,实际调用了原函数。且Mock ocmClient, err := utils.CreateConnection() 时触发错误:

cannot assign to utils.CreateConnection (neither addressable nor a map index expression)

业务代码

func run(cmd *cobra.Command) error {
    response, err := getCurrentOrg()
    if err != nil {
        return fmt.Errorf("invalid input: %q", err)
    }
    acc := Account{}
    json.Unmarshal(response.Bytes(), &acc)
    printOrg(acc.Organization)
    return nil
}

func getCurrentOrg() (*sdk.Response, error) {
    // Create OCM client to talk
    ocmClient, err := utils.CreateConnection()
    if err != nil {
        return nil, err
    }
    defer func() {
        if err := ocmClient.Close(); err != nil {
            fmt.Printf("Cannot close the ocmClient (possible memory leak): %q", err)
        }
    }()
    return sendRequest(createGetCurrentOrgRequest(ocmClient))
}

func createGetCurrentOrgRequest(ocmClient *sdk.Connection) *sdk.Request {
    request := ocmClient.Get()
    err := arguments.ApplyPathArg(request, currentAccountApiPath)
    if err != nil {
        log.Fatalf("Can't parse API path '%s': %v\n", currentAccountApiPath, err)
    }

    return request
}

测试代码

func TestOrgSuite(t *testing.T) {
    ginkgo.RunSpecs(t, "Org Suite")
}

var _ = ginkgo.Describe("getCurrentOrg", func() {
    var (
        apiServer      *ghttp.Server
        ocmClient      *sdk.Connection
        successResp    *Account
        successRespBytes []byte
    )

    // Setup runs before each test case to initialize the fake server and mock dependencies.
    ginkgo.BeforeEach(func() {
        var err error

        // Define a sample success response.
        successResp = &Account{
            Organization: Organization{
                ID:   "org123",
                Name: "TestOrg",
            },
        }
        successRespBytes, err = json.Marshal(successResp)
        gomega.Expect(err).ToNot(gomega.HaveOccurred(), "Failed to marshal success response")

        // Create a fake HTTP server.
        apiServer = ghttp.NewServer()

        // Mock utils.CreateConnection to return a connection to the fake server.
        originalCreateConnection := utils.CreateConnection
        utils.CreateConnection = func() (*sdk.Connection, error) {
            conn, err := sdk.NewConnectionBuilder().
                URL(apiServer.URL()).
                RetryLimit(0). // Disable retries for immediate failure detection
                Insecure(true). // Skip TLS for fake server
                Build()
            if err != nil {
                return nil, err
            }
            ocmClient = conn // Store for cleanup
            return conn, nil
        }
        ginkgo.DeferCleanup(func() {
            utils.CreateConnection = originalCreateConnection // Restore original function
            if ocmClient != nil {
                ocmClient.Close()
            }
            if apiServer != nil {
                apiServer.Close()
            }
        })
    })

    // Test scenarios for getCurrentOrg.
    ginkgo.Describe("fetching current organization", func() {
        ginkgo.It("succeeds with a valid 200 OK response", func() {
            // Configure the fake server to respond to the current account GET request.
            apiServer.AppendHandlers(
                ghttp.CombineHandlers(
                    ghttp.VerifyRequest(http.MethodGet, "/api/accounts_mgmt/v1/current_account"),
                    ghttp.RespondWith(
                        http.StatusOK,
                        successRespBytes,
                        http.Header{"Content-Type": []string{"application/json"}},
                    ),
                ),
            )

            // Call the function under test.
            resp, err := getCurrentOrg()

            // Verify the request succeeds and response matches expectations.
            gomega.Expect(err).ToNot(gomega.HaveOccurred(), "Expected no error fetching current org")
            gomega.Expect(resp).ToNot(gomega.BeNil(), "Expected a non-nil response")
            gomega.Expect(resp.Status()).To(gomega.Equal(http.StatusOK), "Expected 200 OK status")

        })

    })
})

解决方案

1. 报错根源

Go语言中包级普通函数无法直接赋值,utils.CreateConnection 是包级定义的普通函数,因此测试代码中尝试给它赋值的操作会触发报错。同时业务代码硬编码依赖,没有预留替换入口,导致无法Mock依赖。

2. 重构业务代码解耦

有两种常用方案实现可测试性:

方案一:将依赖改为可赋值的函数变量

在utils包中,把原普通函数改为函数类型变量:

// utils包中修改
var CreateConnection = func() (*sdk.Connection, error) {
    // 原CreateConnection的实现逻辑
}

这样测试代码中就能直接替换这个变量,原报错会消失。

方案二:依赖注入(更推荐)

修改getCurrentOrg函数,将创建连接的逻辑作为参数传入,彻底解耦依赖:

// 修改getCurrentOrg函数
func getCurrentOrg(createConn func() (*sdk.Connection, error)) (*sdk.Response, error) {
    ocmClient, err := createConn()
    if err != nil {
        return nil, err
    }
    defer func() {
        if err := ocmClient.Close(); err != nil {
            fmt.Printf("Cannot close the ocmClient (possible memory leak): %q", err)
        }
    }()
    return sendRequest(createGetCurrentOrgRequest(ocmClient))
}

// 修改run函数调用
func run(cmd *cobra.Command) error {
    response, err := getCurrentOrg(utils.CreateConnection)
    if err != nil {
        return fmt.Errorf("invalid input: %q", err)
    }
    // 后续逻辑不变
}

同理,sendRequest也可以用相同方式处理,将其作为参数注入getCurrentOrg,进一步提升可测试性。

3. 适配重构后的测试代码

以依赖注入方案为例,测试时无需修改包级变量,直接在调用getCurrentOrg时传入Mock函数即可:

// 测试用例中修改调用逻辑
resp, err := getCurrentOrg(func() (*sdk.Connection, error) {
    conn, err := sdk.NewConnectionBuilder().
        URL(apiServer.URL()).
        RetryLimit(0).
        Insecure(true).
        Build()
    if err != nil {
        return nil, err
    }
    ocmClient = conn
    return conn, nil
})

同时移除测试代码中替换utils.CreateConnection的逻辑,避免包级变量污染。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 02:10:11