Blog.3 使用httptest模擬接口測試

Test中在模擬接口測試,首先咱們先實現一個最基礎的Test例子:json

模擬一個ping/pong的最基本請求,咱們先寫一個返回pongHTTP handlerapp

import (
    "io"
    "net/http"
    "net/http/httptest"
    "testing"
)

func Pong(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Header().Set("Content-Type", "text/plain")
    io.WriteString(w, "pong")
}

而後寫測試用例:框架

func TestRequest(t *testing.T) {
    req, err := http.NewRequest("GET", "ping", nil)
    if err != nil {
        t.Fatal(err)
    }

    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(Pong)

    handler.ServeHTTP(rr, req)

    if status := rr.Code; status != http.StatusOK {
        t.Errorf("status code %v", rr.Code)
    }

    if rr.Body.String() != "pong" {
        t.Errorf("returned %s", rr.Body.String())
    }
}

程序日誌輸出Pass,這個小demo正常運行了。而後咱們在這個基礎上,咱們給請求增長一個超時時間、以及攜帶header頭等信息測試

咱們將請求的header頭返回,處理的hander以下:ui

func GetUsersHandler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Header().Set("Content-Type", "application/json")

    output, _ := json.Marshal(r.Header)
    io.WriteString(w, string(output))
}

而後是咱們的測試用例, 並且也更貼近咱們的真實開發:日誌

func TestRequest(t *testing.T) {
    req, _ := http.NewRequest("GET", "/user/info", nil)

    // 設置header頭
    req.Header.Set("uid", "10086")

    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(GetUsersHandler)

    // 給請求設置1s的超時
    ctx := req.Context()
    ctx, _ = context.WithTimeout(ctx, time.Second)

    req = req.WithContext(ctx)
    handler.ServeHTTP(rr, req)

    if status := rr.Code; status != http.StatusOK {
        t.Errorf("status code %v", rr.Code)
    }
    t.Log(rr.Body.String())
}

而後咱們追加一個middleware來讓代碼更加真實,middleware的做用就是在context中設置auth的結果。code

// 中間件,在context中設置一個auth
func MiddlewareHandler(h http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        ctx := context.WithValue(r.Context(), "auth", "middle verify")
        h.ServeHTTP(w, r.WithContext(ctx))
    }
    return http.HandlerFunc(fn)
}

連帶的對測試的方法作一下簡單的調整:orm

func TestRequest(t *testing.T) {
    req, _ := http.NewRequest("GET", "/user/info", nil)

    // 設置header頭
    req.Header.Set("uid", "10086")

    // 設置middleware
    rr := httptest.NewRecorder()
    handler := MiddlewareHandler(http.HandlerFunc(GetUsersHandler))

    // 給請求設置1s的超時
    ctx, _ := context.WithTimeout(req.Context(), time.Second)

    req = req.WithContext(ctx)
    handler.ServeHTTP(rr, req)

    if status := rr.Code; status != http.StatusOK {
        t.Errorf("status code %v", rr.Code)
    }
    t.Log(rr.Body.String())
}

最後是將經過middleware傳遞的context值打印輸出:router

func GetUsersHandler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Header().Set("Content-Type", "application/json")

    output, _ := json.Marshal(r.Context().Value("auth"))
    io.WriteString(w, string(output))
}

繼續按照上面的方法,咱們使用gin框架在作一次嘗試。接口/user/info經過Bind的方式來獲取參數。server

咱們在方法體內聲明告終構體,同時將類型定義爲interface。當收到客戶端傳遞的參數時,解析到變量r上,最後json格式打印輸出

func TestRequest(t *testing.T) {
    req, _ := http.NewRequest("GET", "/user/info?type=1", nil)

    // 啓動一個Gin的接口
    router := gin.Default()
    router.GET("/user/info", func(c *gin.Context) {
        type request struct {
            Type interface{} `json:"type" form:"type"`
        }

        r := &request{}
        if err := c.Bind(r); err != nil {
            t.Fatal(err)
        }

        b, _ := json.Marshal(r)
        t.Log(string(b))
    })
    
    // 使用Gin的服務
    rr := httptest.NewRecorder()
    router.ServeHTTP(rr, req)
}

在使用gin進行綁定的時候,返回了錯誤Unknown type

咱們對代碼進行簡單的封裝,這樣就能夠提供一個統一的測試方法,咱們對服務進行封裝

// 將sever的部分抽象出來
func server(w *httptest.ResponseRecorder, r *http.Request) {
    router := gin.Default()
    router.GET("/user/info", func(c *gin.Context) {
        type request struct {
            Type interface{} `json:"type" form:"type"`
        }

        r := &request{}
        if err := c.Bind(r); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"type": 0})
            return
        }

        b, _ := json.Marshal(r)
        c.JSON(http.StatusOK, gin.H{"type": string(b)})
    })

    router.ServeHTTP(w, r)
}

咱們對請求進行封裝:

func TestRequest(t *testing.T) {
    req, _ := http.NewRequest("GET", "/user/info?type=1", nil)

    // 使用Gin的服務
    rr := httptest.NewRecorder()
    server(rr, req)

    p, err := ioutil.ReadAll(rr.Body)
    if err != nil {
        t.Fatal(err)
    }
    t.Log(string(p))
}
相關文章
相關標籤/搜索