💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ## <span style="font-size:15px">**一、说明**</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;httpmock是一款用来模拟http接口请求的工具 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;github地址:`https://github.com/jarcoal/httpmock` 基本使用步骤: * 启动httpmock环境:httpmock.Activate方法 * 路由规则注册:httpmock.RegisterResponder方法。此时,http client发起的请求如果匹配到http mock中注册的规则,则会返回mock response * 在defer里面调用DeactivateAndReset结束mock 官方示例: ``` func TestFetchArticles(t *testing.T) { httpmock.Activate() defer httpmock.DeactivateAndReset() // Exact URL match httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles", httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`)) // Regexp match (could use httpmock.RegisterRegexpResponder instead) httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/\d+\z`, httpmock.NewStringResponder(200, `{"id": 1, "name": "My Great Article"}`)) // do stuff that makes a request to articles ... // get count info httpmock.GetTotalCallCount() // get the amount of calls for the registered responder info := httpmock.GetCallCountInfo() info["GET https://api.mybiz.com/articles"] // number of GET calls made to https://api.mybiz.com/articles info["GET https://api.mybiz.com/articles/id/12"] // number of GET calls made to https://api.mybiz.com/articles/id/12 info[`GET =~^https://api\.mybiz\.com/articles/id/\d+\z`] // number of GET calls made to https://api.mybiz.com/articles/id/<any-number> } ``` ## <span style="font-size:15px">**二、使用示例**</span> 1、示例1:mock固定路由,并mock 字符串响应body ``` func getAPIResponse(url string) (string, error) { var err error request, err := http.NewRequest(http.MethodGet, url, http.NoBody) if err != nil { return "", err } myClient := &http.Client{} response, err := myClient.Do(request) if err != nil { return "", err } defer response.Body.Close() if response.StatusCode != 200 { return "", errors.New("response not 200!") } bodyBytes, err := ioutil.ReadAll(response.Body) if err != nil { return "", err } return string(bodyBytes), nil } ``` ``` func TestGetAPIResponse(t *testing.T) { var api string var responseBody string var err error // http mock test var mockResponse string api = "http://www.baidu.com" mockResponse = "mock response body" httpmock.Activate() defer httpmock.DeactivateAndReset() httpmock.RegisterResponder("GET", api, httpmock.NewStringResponder(200, string(mockResponse))) fmt.Println(httpmock.GetCallCountInfo()) responseBody, err = getAPIResponse(api) if err != nil { t.Errorf("second test failed! err: %v", err) } if responseBody != mockResponse { t.Errorf("second test failed! Unexpect response!") } } === RUN TestGetAPIResponse map[GET http://www.baidu.com:0] --- PASS: TestGetAPIResponse (0.00s) PASS ok sangfor.com/xdr/xlink/common/util 0.041s ``` 2、示例2:mock固定路由,并mock json响应body ``` type Response struct { Message string `json:"message"` } func TestHttpMock(t *testing.T) { httpmock.Activate() url := "http://xxxxxx" responder, _ := httpmock.NewJsonResponder(http.StatusOK, Response{Message: "success"}) httpmock.RegisterResponder("GET", url, responder) response := &Response{} client := http.Client{} req, err := http.NewRequest(http.MethodGet, url, http.NoBody) res, _ := client.Do(req) defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body) err = json.Unmarshal(body, &response) if err != nil { t.Errorf("fail") } fmt.Println(response.Message) } ``` 3、示例3:自定义mock http response 返回 ``` api = "http://www.baidu.com/fail" httpmock.RegisterResponder("GET", api, func(r *http.Request) (*http.Response, error) { return &http.Response{ Status: strconv.Itoa(200), StatusCode: 200, ContentLength: -1, }, errors.New("fail error!") }) responseBody, err = getAPIResponse(api) if err == nil { t.Errorf("request fail test failed! err: %v", err) } if responseBody != "" { t.Errorf("request fail test failed! Unexpect response! response: %s", responseBody) } ``` 4、示例4:对设置Transport的http client进行mock 对于设置了Transport的http请求,可以使用`httpmock.ActivateNonDefaul `方法进行mock,并支持传入对应的client。 ``` func TestHttpTransportMock(t *testing.T) { response := &Response{} client := http.Client{ Timeout: time.Second * 3, Transport: &http.Transport{ DisableKeepAlives: true, }, } httpmock.ActivateNonDefault(&client) defer httpmock.DeactivateAndReset() url := "http://xxxxxx" responder, _ := httpmock.NewJsonResponder(http.StatusOK, &Response{Message: "success"}) httpmock.RegisterResponder("GET", url, responder) httpmock.NewMockTransport() req, err := http.NewRequest(http.MethodGet, url, http.NoBody) if err != nil { t.Errorf("failed to create request: %v", err) return } res, err := client.Do(req) if err != nil { t.Errorf("failed to perform request: %v", err) return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Errorf("failed to read response body: %v", err) return } err = json.Unmarshal(body, &response) if err != nil { t.Errorf("failed to unmarshal response: %v", err) return } fmt.Println(response.Message) } ``` 4、示例5:对设置Transport的http client进行mock不成功的情况 ``` func getApiResponse(url string) (*openapi, error) { client := http.Client{ Timeout: time.Second * 3, Transport: &http.Transport{ DisableKeepAlives: true, }, } log.Infof("client:%v", &client) req, err := http.NewRequest(http.MethodGet, url , http.NoBody) if err != nil { return nil, fmt.Errorf(" err=%v", err) } resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf(" client.Do err=%v", err) } defer resp.Body.Close() buff, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf(" body err=%v", err) } temp := &openapiAKSK{} err = json.Unmarshal(buff, temp) if err != nil { return nil, fmt.Errorf("json.Unmarshal err=%v", err) } return temp, nil } // 编写单测 func TestGetApiResponse(t *testing.T) { client := http.Client{ Timeout: time.Second * 3, Transport: &http.Transport{ DisableKeepAlives: true, }, } log.Infof("client1:%v", &client) httpmock.ActivateNonDefault(&client) defer httpmock.DeactivateAndReset() resp, _ := httpmock.NewJsonResponder(200, Response{Data: struct { AK string `json:"ak"` }(struct{ AK string }{AK: "mockedAK"})}) httpmock.RegisterResponder( "GET", "http://openapi.platform.svc.cluster.local", resp) result, err := getApiResponse("http://openapi.platform.svc.cluster.local") if err != nil { t.Errorf("Expected no error, but got %v", err) } if result.Data.AK != "mockedAK" { t.Errorf("Expected to be 'mockedAK', but got %s", result.Data.AK) } } === RUN TestGetApiResponse devicesingleloginHandler_test.go:85: Expected no error, but got devsso getOpenapiAKSK client.Do err=Get "http://openapi.platform.svc.cluster.local": dial tcp: lookup openapi.platform.svc.cluster.local: no such host --- FAIL: TestGetApiResponse(0.20s) panic: runtime error: invalid memory address or nil pointer dereference [recovered] panic: runtime error: invalid memory address or nil pointer dereference ``` &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;执行结果会发现,单测中已经使用了`httpmock.ActivateNonDefaul `,并且设置了一样的Transport,执行失败。 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这是因为代码逻辑和单测中,分别初始化了http client,这两个实际上不是同一个client。 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;通过日志打印可以看出,两个client的指针地址并不相同。 ``` 2023/12/26 15:48:31 consolewriter.go:51: {"msg":"client1:\u0026{0xc0000de3c0 \u003cnil\u003e \u003cnil\u003e 3s}"} map[GET http://openapi.platform.svc.cluster.local:0] 2023/12/26 15:48:31 consolewriter.go:51: {"msg":"client:\u0026{0xc0000de500 \u003cnil\u003e \u003cnil\u003e 3s}"} ``` &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;因此,需要把代码修改为client传入的方式,确保代码和单测可以使用同一个client。 ``` func getApiResponse(url string, client *http.Client) (*openapi, error) { req, err := http.NewRequest(http.MethodGet, url , http.NoBody) if err != nil { return nil, fmt.Errorf(" err=%v", err) } resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf(" client.Do err=%v", err) } defer resp.Body.Close() buff, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf(" body err=%v", err) } temp := &openapiAKSK{} err = json.Unmarshal(buff, temp) if err != nil { return nil, fmt.Errorf("json.Unmarshal err=%v", err) } return temp, nil } // 编写单测 func TestGetApiResponse(t *testing.T) { client := http.Client{ Timeout: time.Second * 3, Transport: &http.Transport{ DisableKeepAlives: true, }, } log.Infof("client1:%v", &client) httpmock.ActivateNonDefault(&client) defer httpmock.DeactivateAndReset() resp, _ := httpmock.NewJsonResponder(200, Response{Data: struct { AK string `json:"ak"` }(struct{ AK string }{AK: "mockedAK"})}) httpmock.RegisterResponder( "GET", "http://openapi.platform.svc.cluster.local", resp) result, err := getApiResponse("http://openapi.platform.svc.cluster.local", client) if err != nil { t.Errorf("Expected no error, but got %v", err) } if result.Data.AK != "mockedAK" { t.Errorf("Expected to be 'mockedAK', but got %s", result.Data.AK) } } ```