在Golang的handler函数中如何注入client

在Golang的handler函数中如何注入client 我正在测试一个场景,希望在"CreateEmployee"函数中注入客户端,仅用于测试用例而不影响生产/预发布环境。

我的处理函数如下所示…

// 在系统中创建员工
func CreateEmployee(w http.ResponseWriter, r *http.Request) {
    /// 此处有一些代码,将请求体中的JSON映射到HTTP请求...等等

    response, e := client.Do(request)  // 这是http.Client
}

我正在编写一个测试用例,如下所示

func TestCreateEmployee(t *testing.T) {

    var jsonStr = []byte(`{员工详情JSON}`)

    req, err := http.NewRequest("POST", "http://www.exapmle.com/empsys/", bytes.NewBuffer(jsonStr))
    
    resp := httptest.NewRecorder()

    CreateEmployee(resp,req) // 我在此处调用我的函数,但不确定client.Do会如何表现..
    /// 以及后续代码
}

有人能建议测试这种情况的好方法吗?


更多关于在Golang的handler函数中如何注入client的实战教程也可以访问 https://www.itying.com/category-94-b0.html

9 回复

我该怎么办……我毫无头绪。是否可以硬编码一些响应……或者应该修改现有代码,使其具备可测试性

更多关于在Golang的handler函数中如何注入client的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我认为这个存根的想法对我来说可行……我会尝试一下……你知道有什么在线的存根示例吗?

在这个函数中,我对JSON对象(作为请求体接收)进行编组和解组,并将请求转发给第三方

那么当你调用 client.Do 时就必须捕获这种情况。但由于成功或失败并非由你掌控,你的测试将不具备确定性。这不是一个好主意。

func main() {
    fmt.Println("hello world")
}

client 正在执行的 request 是什么?

这看起来像是一个集成测试,因为它依赖于第三方系统,即 client 连接的主机。该主机是否始终可用?

您是否想要隐藏在此注释后面的代码?

// some code here, mapping json from request body to http request...etc

请求示例是创建一个 Keycloak 用户,但 Keycloak 并非始终可用。

当我调用 client.Do(request) 时… 它会创建用户并返回一些响应

我不需要代码… 但每次调用 client.Do(request) 时总是遇到错误,所以想要模拟 Do 方法

client.Do(request)

存根代码会像这样吗

func main() {
	mock := &ClientMock{}
	err := TestMyClient(mock)
	if err != nil {
		fmt.Println(err.Error())
	}
}
type HttpClient interface {
	Do(req *http.Request) (*http.Response, error)
}
func (c *ClientMock) Do(req *http.Request) (*http.Response, error) {
	return &http.Response{}, nil
}
type ClientMock struct {
}

我不建议对 Do 调用返回空值。

再次强调,你想要测试什么?CreateEmployee 具体在做什么?这个函数中除了向远程服务器发送请求外,肯定还有其他逻辑。你的测试代码应该验证在给定明确定义的请求时,这个 HTTP 处理程序能否正确工作。CreateEmployee 在远程调用前后执行了哪些操作?这些才是你需要测试的内容。桩函数可以帮助你确保远程调用始终稳定运行。

func main() {
    fmt.Println("hello world")
}

想要做什么?测试依赖于第三方服务的代码不是一个好主意。

你可以为第三方服务编写一个桩服务器:找出它提供的HTTP API的相关部分,以及你将调用client.Do的部分。实现一个HTTP服务,该服务对这些API部分进行桩模拟,并根据已知请求返回预定义的数据。然后编写测试,使client.Do通过这些请求调用桩服务器,并测试你的代码是否正确处理了响应。

在Golang中测试handler函数时,为了控制HTTP客户端的行为,可以使用依赖注入模式。以下是几种实现方法:

方法1:使用接口包装http.Client

首先定义一个HTTP客户端接口:

type HTTPClient interface {
    Do(req *http.Request) (*http.Response, error)
}

type CustomClient struct {
    Client HTTPClient
}

// 在生产环境中使用真实的http.Client
func NewCustomClient() *CustomClient {
    return &CustomClient{
        Client: &http.Client{},
    }
}

// 在handler中通过参数传入
func CreateEmployee(w http.ResponseWriter, r *http.Request, client HTTPClient) {
    // 原有代码...
    response, e := client.Do(request)
    // 处理响应...
}

方法2:使用全局变量和测试替换

var (
    httpClient HTTPClient = &http.Client{}
)

func CreateEmployee(w http.ResponseWriter, r *http.Request) {
    // 使用全局的httpClient
    response, e := httpClient.Do(request)
    // 处理响应...
}

// 在测试中替换httpClient
func TestCreateEmployee(t *testing.T) {
    // 保存原始客户端
    originalClient := httpClient
    defer func() { httpClient = originalClient }()
    
    // 使用模拟客户端
    httpClient = &mockHTTPClient{}
    
    // 执行测试
    var jsonStr = []byte(`{"name":"John","age":30}`)
    req, _ := http.NewRequest("POST", "/employees", bytes.NewBuffer(jsonStr))
    resp := httptest.NewRecorder()
    
    CreateEmployee(resp, req)
    // 验证结果...
}

方法3:使用结构体和依赖注入

type EmployeeHandler struct {
    Client HTTPClient
}

func NewEmployeeHandler(client HTTPClient) *EmployeeHandler {
    if client == nil {
        client = &http.Client{}
    }
    return &EmployeeHandler{Client: client}
}

func (h *EmployeeHandler) CreateEmployee(w http.ResponseWriter, r *http.Request) {
    response, e := h.Client.Do(request)
    // 处理响应...
}

// 测试代码
func TestCreateEmployee(t *testing.T) {
    // 创建模拟客户端
    mockClient := &mockHTTPClient{}
    handler := NewEmployeeHandler(mockClient)
    
    var jsonStr = []byte(`{"name":"John","age":30}`)
    req, _ := http.NewRequest("POST", "/employees", bytes.NewBuffer(jsonStr))
    resp := httptest.NewRecorder()
    
    handler.CreateEmployee(resp, req)
    // 验证结果...
}

模拟HTTP客户端实现

type mockHTTPClient struct{}

func (m *mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
    // 返回模拟响应
    return &http.Response{
        StatusCode: http.StatusOK,
        Body:       io.NopCloser(bytes.NewBufferString(`{"id":123,"status":"success"}`)),
        Header:     make(http.Header),
    }, nil
}

方法4:使用函数类型

type DoFunc func(*http.Request) (*http.Response, error)

func CreateEmployee(w http.ResponseWriter, r *http.Request, doFunc DoFunc) {
    if doFunc == nil {
        doFunc = http.DefaultClient.Do
    }
    
    response, e := doFunc(request)
    // 处理响应...
}

// 测试中使用
func TestCreateEmployee(t *testing.T) {
    mockDo := func(req *http.Request) (*http.Response, error) {
        return &http.Response{
            StatusCode: http.StatusCreated,
            Body:       io.NopCloser(bytes.NewBufferString(`{"id":1}`)),
        }, nil
    }
    
    var jsonStr = []byte(`{"name":"John"}`)
    req, _ := http.NewRequest("POST", "/employees", bytes.NewBuffer(jsonStr))
    resp := httptest.NewRecorder()
    
    CreateEmployee(resp, req, mockDo)
}

推荐使用方法3(结构体依赖注入),因为它提供了最好的可测试性和清晰的关注点分离。在生产代码中,你可以使用真实的HTTP客户端,在测试中注入模拟实现。

回到顶部