Blog.3 使用httptest模拟接口测试

渐行渐远 · · 1219 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Test中在模拟接口测试,首先我们先实现一个最基础的Test例子:

模拟一个ping/pong的最基本请求,我们先写一个返回pongHTTP handler

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如下:


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的结果。

// 中间件,在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)
}

连带的对测试的方法做一下简单的调整:

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值打印输出:

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的方式来获取参数。

我们在方法体内声明了结构体,同时将类型定义为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))
}

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:渐行渐远

查看原文:Blog.3 使用httptest模拟接口测试

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1219 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传