Go 如何模擬 HTTP 請求

這一篇,我們來學習一下如何使用 httpmock 庫,當使用 Go 語言編寫 HTTP 服務時,可以使用第三方庫來模擬 HTTP 請求和響應,例如:httpmock 庫。這個庫提供了一些方便的函數和結構體,用於在測試中模擬 HTTP 請求和驗證期望的 HTTP 調用。

輕鬆模擬來自外部資源的 http 響應。

https://github.com/jarcoal/httpmock

01 服務端

我們先使用 net/http 構建幾個接口,方便我們來模擬。

package main
import (
   "context"
   "fmt"
   "net/http"
)
func main() {
   mux := http.NewServeMux()
   mux.HandleFunc("/", index)
   mux.Handle("/foo", http.HandlerFunc(foo))
   mux.Handle("/bar", http.HandlerFunc(bar))
   server := http.Server{
      Addr:    ":8081",
      Handler: mux,
   }
   server.ListenAndServe()
}
func index(w http.ResponseWriter, r *http.Request) {
   fmt.Println("index")
   w.Header().Set("Content-Type", "application/json; charset=utf-8")
   w.Write([]byte(`{"name": "index"}`))
}
func foo(w http.ResponseWriter, r *http.Request) {
   fmt.Println("foo")
   w.Header().Set("Content-Type", "application/json; charset=utf-8")
   w.Write([]byte(`{"name": "foo"}`))
}
func bar(w http.ResponseWriter, r *http.Request) {
   fmt.Println("bar")
   w.Header().Set("Content-Type", "application/json; charset=utf-8")
   w.Write([]byte(`{"name": "bar"}`))
}

運行完上面代碼,我們實現了 3 個接口,分別是 /,/foo,/bar !

02 httpmock 簡單示例

         

針對上面的接口,如果我們想用代碼去訪問 http 請求,可以是這樣子的:

func TestHttpGet(t *testing.T) {
   // 對 foo 發送GET請求
   resp, err := http.Get("http://localhost:8081/foo")
   if err != nil {
      t.Fatalf("Failed to send GET request: %v", err)
   }
   body, _ := ioutil.ReadAll(resp.Body)
   resp.Body.Close()
   t.Log(string(body))
}

輸出結果是:{"name": "foo"}

下面,我們使用 httpmock 來模擬我們的 http 請求,讓它返回我們想要的結果,請看下面示例:

package main
import (
   "github.com/jarcoal/httpmock"
   "io/ioutil"
   "net/http"
   "testing"
)
func TestHttpMockGetRequest(t *testing.T) {
   httpmock.Activate()
   defer httpmock.DeactivateAndReset()
   expectedResponse := "Hello, World!"
   httpmock.RegisterResponder("GET", "http://localhost:8081/foo",
      httpmock.NewStringResponder(200, expectedResponse))
httpmock.RegisterResponder("GET", "http://localhost:8081/bar",
      httpmock.NewStringResponder(200, expectedResponse))
   // 對 foo 發送GET請求
   resp, err := http.Get("http://localhost:8081/foo")
   if err != nil {
      t.Fatalf("Failed to send GET request: %v", err)
   }
   // 驗證響應
   body, _ := ioutil.ReadAll(resp.Body)
   resp.Body.Close()
   if string(body) != expectedResponse {
      t.Errorf("Unexpected response. Expected: %s, Got: %s", expectedResponse, string(body))
   }
   // 對 bar 發送GET請求
   resp, err = http.Get("http://localhost:8081/bar")
   if err != nil {
      t.Fatalf("Failed to send GET request: %v", err)
   }
   // 驗證響應
   body, _ = ioutil.ReadAll(resp.Body)
   resp.Body.Close()
   if string(body) != expectedResponse {
      t.Errorf("Unexpected response. Expected: %s, Got: %s", expectedResponse, string(body))
   }
   count := httpmock.GetTotalCallCount()
   t.Log(count)
}

現在,一起看解析一下上面代碼的含義。

運行上面的測試用例,原先 foo 接口和 bar 接口分別返回的是 **{"name": "foo"} 和 **{"name": "bar"}****。經過我們修改後,它們都將返回 Hello, World! 與此同時,count 的值爲 2。

03 httpmock 高級示例

         

在上面的示例中,httpmock 直接匹配的是:請求方法和 URL 路徑。下面我們來看看 httpmock 提供的正則表達式匹配。

func TestHttpMockRegexpRequest(t *testing.T) {
   httpmock.Activate()
   defer httpmock.DeactivateAndReset()
   httpmock.RegisterResponder("GET", `=~^http://localhost:8081/(\w+)\z`,
      func(req *http.Request) (*http.Response, error) {
         name := httpmock.MustGetSubmatch(req, 1) // 1=first regexp submatch
         fmt.Println(name)
         return httpmock.NewJsonResponse(200, map[string]interface{}{
            "name": "My name is " + name,
         })
      })
   resp, err := http.Get("http://localhost:8081/bar")
   if err != nil {
      t.Fatalf("Failed to send GET request: %v", err)
   }
   // 驗證響應
   body, _ := ioutil.ReadAll(resp.Body)
   resp.Body.Close()
   t.Log(string(body))
}

與先前的示例不同,現在我們的 URL 是使用了正則表達式:

  =~^http://localhost:8081/(\w+)\z 是一個正則表達式模式。讓我來解釋一下它的含義:

因此,我們輸入 http://localhost:8081/bar 作爲測試例子,就能夠被完全匹配了,並且輸出:{"name":"My name is bar"}。

04 resty 與 httpmock 結合

在日常開發中,我比較喜歡用 go-resty 庫,go-resty 庫是 Go 語言中一個簡單的 HTTP 和 Resty 客戶端庫,其涵蓋了比較多的功能,開箱即用,很方便。下面一起來看看如何把 resty 也使用 httpmock 請求。

首先,我們編寫一段客戶端代碼,用於請求上面定義的接口,代碼如下:

package main
import (
   "context"
   "fmt"
   "github.com/go-resty/resty/v2"
)
func Request(client *resty.Client) (*Result, error) {
   url := "http://localhost:8081"
   result := &Result{}
resp, err := client.SetTimeout(time.Second * 5).
    R().SetContext(context.Background()).SetResult(result).Get(url)
// 出現網絡等請求錯誤
   if err != nil {
      return nil, err
   }
   // 響應碼不是 200
   if resp.StatusCode() != http.StatusOK {
      return nil, fmt.Errorf("status code: %d", resp.StatusCode())
   }
   return result, nil
}

下面我們直接看看 Request 方法的測試用例如何寫:

func TestRequest(t *testing.T) {
   client := resty.New()
   method := "GET"
   url := "http://localhost:8081"
   convey.Convey("Test Request", t, func() {
      // 1. 模擬出現網絡錯誤
      convey.Convey("模擬出現網絡錯誤", func() {
         httpmock.ActivateNonDefault(client.GetClient())
         defer httpmock.DeactivateAndReset()
         httpmock.RegisterResponder(method, url, func(req *http.Request) (*http.Response, error) {
            return nil, fmt.Errorf("network error")
         })
         _, err := Request(client)
         convey.So(err, convey.ShouldNotBeNil)
      })
      // 2. 模擬出現500錯誤碼
      convey.Convey("模擬出現500錯誤碼", func() {
         httpmock.ActivateNonDefault(client.GetClient())
         defer httpmock.DeactivateAndReset()
         httpmock.RegisterResponder(method, url, httpmock.NewStringResponder(500, ""))
         _, err := Request(client)
         convey.So(err, convey.ShouldNotBeNil)
      })
      // 3. 模擬正常請求
      convey.Convey("模擬正常請求", func() {
         httpmock.ActivateNonDefault(client.GetClient())
         defer httpmock.DeactivateAndReset()
         httpmock.RegisterResponder(method, url, func(req *http.Request) (*http.Response, error) {
            return httpmock.NewJsonResponse(200, &Result{Name: "success"})
         })
         resp, err := Request(client)
         convey.So(err, convey.ShouldBeNil)
         convey.So(resp.Name, convey.ShouldEqual, "success")
      })
   })
}

一個完整的測試用例就呈現了,代碼的核心是:

httpmock.ActivateNonDefault(client.GetClient())

我們需要將 client 作爲參數傳入到 Request 方法中,纔可以讓 httpmock 去使用。

05 總結

httpmock 是一個用法比較簡單的庫,如果你平時有寫測試用例,應該是會用到的啦,這篇文章分享給你,希望對你有用!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/5TiJxK7i5ggizYWP_MbCxg