无法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
相关产品推荐
相关产品推荐

