在Test
中在模拟接口测试,首先我们先实现一个最基础的Test
例子:
模拟一个ping/pong
的最基本请求,我们先写一个返回pong
的HTTP 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))
}