简要介绍如何在单元测试过程中,使用httptest包,进行http模拟测试
1 httptest包简要介绍
net/http/httptest
包提供了一些HTTP测试有用的工具,通常使用httptest包的流程:模拟一个真实的HTTP Server,设置Handler函数,模拟http请求(利用http包),断言结果。
在HTTP Server的模拟上,主要由两类,一类是利用httptest.NewRecorder()
方法仅做http.ResponseWriter
接口实现,然后模拟http请求的流程,实际上是没有任何真实的HTTP网络监听的;另一类是诸如httptest.NewServer()
、httptest.NewTLSServer()
、httptest.NewUnstartedServer()
会有真实的网络套接字监听,测试完成后关闭套接字,后面几个使用方式都类似,后面简要介绍下它们的差别。
- 实现
http.ResponseWriter
接口,通过Mock HTTP Response
模拟HTTP响应w := httptest.NewRecorder()
req := httptest.NewRequest()
resp := handler(w, req)
- 真实创建HTTP测试服务,创建后就立马监听,通过
http.Get(ts.URL)
、http.Post(ts.URL)
等发起真实HTTP请求ts := httptest.NewServer()
ts := httptest.NewTLSServer()
- 真实创建HTTP测试服务,创建后可以通过
ts
来说设置相关参数,并需要通过StartTLS()
启动HTTP测试服务,再进行HTTP请求测试ts := httptest.NewUnstartedServer()
ts.EnableHTTP2 = true
: 设置参数ts.StartTLS()
:启动
另外,注意真实创建HTTP测试服务,需要在UT结束时候关闭打开的资源,包括套接字资源:defer ts.Close()
以及defer res.Body.Close()
。
1.1 通过NewRecorder() 模拟HTTP请求和响应
以下例子中:
- 通过
req := httptest.NewRequest()
模拟HTTP请求 - 通过
w := httptest.NewRecorder()
模拟HTTP响应 - 通过
handler(w, req)
函数模拟HTTP处理过程, - 最后通过
w.Result()
获取函数处理结果,并以ioutil.ReadAll()
函数读取
实际整个过程没有任何网络连接的过程,相当于只是http.ResponseWriter
接口的实现,并mock HTTP请求数据返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| func TestRespRecorder(t *testing.T) {
// define handler
handler := func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "<html><body>Hello World!</body></html>")
}
// define request
req := httptest.NewRequest("GET", "http://httpbin.org/ip", nil)
t.Logf("%+v", req)
// new an recorder impl http.ResponseWriter
w := httptest.NewRecorder()
// handle
handler(w, req)
// get fake http response
resp := w.Result()
body, _ := ioutil.ReadAll(resp.Body)
t.Logf("%+v", resp)
t.Log(string(body))
}
|
1.2 通过NewUnstartedServer()创建测试服务器、设置参数、显示启动测试HTTP服务
该示例通过ts := httptest.NewUnstartedServer()
创建了一个真实的HTTP测试服务,并通过ts.EnabledHTTP2=true
设置服务器参数,最后通过ts.StartTLS()
启动测试服务;
因为是真实的网络监听,因此需要在UT结束时候,把套接字关闭,因此需要执行defer ts.Closer()
;
另外,发起请求是通过res, err := ts.Client().Get(ts.URL)
实现,ts.Client()就是生成的HTTP Client,ts.URL就是模拟出来的测试URL(比如https://127.0.0.1:56145
);
后续http response处理,与正常的http response处理过程无差异,比如资源文件打开的关闭操作:res.Body.Close()
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| func TestUnstartedHTTP2(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s", r.Proto)
}
// 创建一个server但并不启动他
ts := httptest.NewUnstartedServer(http.HandlerFunc(handler))
// 设置
ts.EnableHTTP2 = true
// 启动
ts.StartTLS()
t.Logf("http server addr: %s", ts.URL)
defer ts.Close()
// 请求测试地址
res, err := ts.Client().Get(ts.URL)
if err != nil {
log.Fatal(err)
}
greeting, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Fatal(err)
}
res.Body.Close()
t.Logf("%s", greeting)
}
|
1.3 通过NewServer()、NewTLSServer() 创建HTTP测试服务
httptest.NewServer()
与httptest.NewUnstartedServer()
类似,但NewServer()是默认就启动了HTTP服务,不像httptest.NewUnstartedServer()
还需要显示的执行StartTLS()
方法。
httptest.NewTLSServer()
是在httptest.NewServer()
基础上,增加了TLS加密传输特性,用于模拟HTTPS请求测试。
另外,默认情况下通过http.HandlerFunc(handler)
返回的http Content-Type响应是text/plain
,有需要可以通过w.Header().Set("Content-Type", "application/json")
进行显示设置HTTP响应头。
1.3.1 NewServer()模拟服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| func TestNormalHTTPServer(t *testing.T) {
// 创建一个普通HTTP1.1测试服务
handler := func(w http.ResponseWriter, r *http.Request) {
// json header
w.Header().Set("Content-Type", "application/json")
// json回包
m := map[string]interface{}{
"id": 100,
"name": "clark",
"likes": []string{"play game", "watch tv", "read book",},
}
jdt, err := json.Marshal(m)
if err != nil {
t.Fatal(err)
}
w.Write(jdt)
// fmt.Fprintf(w, "%s", jdt)
}
ts := httptest.NewServer(http.HandlerFunc(handler))
defer ts.Close()
// 发起http请求
res, err := http.Get(ts.URL)
if err != nil {
log.Fatal(err)
}
t.Logf("%s", res.Header)
t.Logf("%s", res.Request.URL)
// http响应处理
greeting, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatal(err)
}
t.Logf("%s", greeting)
}
|
1.3.2 NewTLSServer()模拟TLS服务器
通过res, err := ts.Client().Get(ts.URL)
,ts.Client
模拟了一个HTTP Client,配置了TLS证书信任,并在Server.Close()
时候关闭闲置连接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| func TestTLSHTTP(t *testing.T) {
// 直接创建一个TLS测试Server
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, client")
}))
defer ts.Close()
// 请求测试地址
res, err := ts.Client().Get(ts.URL)
if err != nil {
log.Fatal(err)
}
greeting, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatal(err)
}
t.Logf("%s", greeting)
}
|