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

Go语言中如何通过依赖注入Mock getData函数并完成单元测试

在Go中通过依赖注入实现生产与测试环境的URL切换及单元测试

你的思路完全正确——依赖注入确实是解决这类环境隔离测试问题的核心方案。咱们把现有代码调整得更规范、更易维护,同时完美满足生产用真实URL、测试用httptest模拟服务的需求。

一、先修正生产代码的潜在问题

先把现有代码里的小瑕疵(比如资源泄漏、可读性差)解决掉:

1. 优化iData接口的可读性

原来的getData()返回无意义的字符串数组,靠索引取参数太容易出错。咱们改成更清晰的接口定义:

// 替换原有的iData接口,明确返回业务所需的配置信息
type iData interface {
	GetAuth() (username, password string)
	GetFullURL() string
}

// 生产环境的配置实现
type ProdData struct {
	Username string
	Password string
	BaseURL  string
	Path     string
}

func (p ProdData) GetAuth() (string, string) {
	return p.Username, p.Password
}

func (p ProdData) GetFullURL() string {
	return p.BaseURL + p.Path
}

2. 修复HTTP请求的资源泄漏

原来的defer res.Body.Close()放在了读取响应之后,若读取失败会导致资源泄漏,咱们提前到获取响应后:

func (i impl) HTTPRequest(username, password, method, url string) ([]byte, error) {
	req, err := http.NewRequest(method, url, nil)
	if err != nil {
		return nil, err
	}
	req.SetBasicAuth(username, password)

	res, err := i.client.Do(req)
	if err != nil {
		return nil, err
	}
	// 提前defer,确保无论后续是否出错,响应体都会被关闭
	defer res.Body.Close()

	token, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}
	fmt.Println("request success")
	return token, nil
}

3. 调整业务方法的可测试性

把caller改成返回错误,方便测试时断言请求结果:

func (s *ServiceInfo) caller() error {
	username, password := s.data.GetAuth()
	fullURL := s.data.GetFullURL()
	// 把HTTP请求的错误返回,便于测试断言
	_, err := s.req.HTTPRequest(username, password, "POST", fullURL)
	return err
}

二、实现测试用的Mock配置

定义一个Mock版的配置结构体,实现iData接口,返回测试服务器的URL和预设的认证信息:

// 测试环境的Mock配置
type MockData struct {
	TestURL  string
	Username string
	Password string
}

func (m MockData) GetAuth() (string, string) {
	return m.Username, m.Password
}

func (m MockData) GetFullURL() string {
	return m.TestURL
}

三、完善单元测试代码

用httptest.NewServer模拟真实HTTP服务,验证请求的认证信息是否正确:

import (
	"net/http"
	"net/http/httptest"
	"github.com/onsi/gomega"
	"testing"
)

func TestServiceCaller(t *testing.T) {
	gomega.RegisterTestingT(t)

	// 启动测试服务器,验证请求的BasicAuth信息
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		u, p, ok := r.BasicAuth()
		gomega.Expect(ok).To(gomega.Equal(true))
		gomega.Expect(u).To(gomega.Equal("test-user"))
		gomega.Expect(p).To(gomega.Equal("test-pass"))

		// 返回模拟的响应内容
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"access_token":"test-token"}`))
	}))
	defer testServer.Close() // 测试结束后关闭服务器

	// 注入测试依赖
	httpClient := &http.Client{}
	mockConfig := MockData{
		TestURL:  testServer.URL,
		Username: "test-user",
		Password: "test-pass",
	}
	service := NewServiceInfo(impl{client: httpClient}, mockConfig)

	// 调用业务方法并断言无错误
	err := service.caller()
	gomega.Expect(err).To(gomega.BeNil())
}

四、生产环境的初始化代码

生产环境只需注入真实的配置实例即可:

func main() {
	prodConfig := ProdData{
		Username: "real-user",
		Password: "real-pass",
		BaseURL:  "http://www.mocky.io/v2/5c20eccc2e00005c001e0c84",
		Path:     "/oauth/token?grant_type=client_credentials",
	}
	httpClient := &http.Client{}
	service := NewServiceInfo(impl{client: httpClient}, prodConfig)

	if err := service.caller(); err != nil {
		fmt.Printf("request failed: %v\n", err)
	}
}

核心逻辑总结

  • 依赖注入:通过将requester和iData接口作为ServiceInfo的依赖,而非硬编码实现,让我们能在测试时轻松替换成Mock版本。
  • 接口隔离:iData接口负责提供业务配置,生产和测试环境分别实现该接口,完美实现环境隔离。
  • 可控测试:用httptest.NewServer模拟真实服务,既能控制响应内容,又能验证请求的正确性(比如认证信息、请求方法)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:17:40