很多时候我们会使用 golang 来作为 web
服务的后端,这时候我们就需要对我们的 http 接口进行测试。Go
语言的标准库提供了一个非常方便的测试工具:net/http/httptest
包,可以用来测试 http 服务。 比如对自己写的 http 接口进行测试,通过 mock
掉 ResponseWriter
;又或者在单元测试中 mock 掉对外部的 http
请求,让我们的测试不依赖于外部的 http 服务。
http 测试的作用
- 测试你写的 http 接口:具体来说,就是测试你的 http handler 是否按照预期工作。比如你的 handler 是否正确的处理了请求参数,是否正确的返回了响应。
- mock 掉对外部的 http 请求:在单元测试中,我们通常不希望依赖于外部的 http 服务,因为外部的服务可能会有变化,这样会导致我们的测试不稳定。
如何测试你的 http 接口
假设我们有一个返回 Hello, world!
的 http 接口:
1 | package main |
我们单元测试中,可能会想知道,自己构建一个 http.Request
得到的响应是否是我们预期的。比如我们想知道,是否返回了
Hello, World!
,状态码是否是 200 等。
这个时候,我们就可以使用 httptest.NewRecorder
来模拟一个
http.ResponseWriter
,它跟 http.ResponseWriter
的行为一样,
但是它不会真的发送响应到客户端,而是把响应保存在内存中,这样我们就可以方便的测试我们的
handler 是否按照预期工作:
1 | func TestHelloHandler(t *testing.T) { |
说明:
- 我们使用
httptest.NewRequest
来创建一个请求,这个请求可以用来模拟一个 http 请求。 - 我们使用
httptest.NewRecorder
来创建一个响应,这个响应可以用来模拟一个http.ResponseWriter
。 - 调用
HelloHandler
的过程实际上等同于我们的 http 服务接收到了一个请求,然后返回了一个响应。 - 最后我们检查了响应码和响应体是否符合预期。
httptest.NewRecorder
返回了一个
ResponseRecorder
实例,它有如下字段:
Code
记录了响应的状态码Body
记录了响应的 bodyHeaderMap
记录了响应的 headerFlushed
记录了响应是否已经被发送
它具有如下方法:
Header()
返回响应的 headerResult()
返回响应的http.Response
实例Write([]byte)
写入响应的 body(它的调用发生在我们的 http handler 中)WriteString(string)
写入响应的 body(跟Write([]byte)
类似,只是参数类型不一样)WriteHeader(int)
写入响应的状态码Flush()
将响应发送到客户端
总的来说,我们可以通过 httptest.NewRecorder
来模拟一个
http.ResponseWriter
,然后调用我们的
handler,最后检查响应是否符合预期。
也就是说,我们不用启动一个真正的 http
客户端来对我们的接口进行测试;当然,我们也不需要真的启动一个真正的 http
服务。
mock 掉对外部的 http 请求
有时候,我们开发的功能会依赖于外部的 http 服务,比如调用一个第三方的接口。在单元测试中,我们通常不希望依赖于外部的服务,因为外部的服务可能会有变化,这样会导致我们的测试不稳定。
如何 mock ?
- 定义一个
http.Handler
的实现,然后在这个 handler 中返回我们预期的响应 - 使用
httptest.NewServer
来启动一个 mock http 服务,使用上面的 handler - 使用
httptest.Server
实例的URL
字段来获取这个 mock 服务的地址 - 最终,当我们访问这个 mock 服务的时候,它会返回我们预期的响应
示例
先定义一个 http.Handler
,我们判断请求的路径是
/hello
的时候,返回 Hello, World!
:
1 | package main |
接着,启动一个 httptest.Server
,并指定我们的
HandlerFunc
:
1 | server := httptest.NewServer(&MyHandler{}) |
这里获取到的 server.URL
就是我们的 mock
服务的地址,我们可以通过这个地址来访问我们的 mock 服务。
我们可以将自己代码中访问外部服务的地址替换为
server.URL
,这样我们的测试就不会依赖于外部服务,而是依赖于我们自己的 mock 服务。这样就可以实现对外部服务的 mock。
下面是一个完整的示例:
1 | // myFunc 请求 url 并返回响应 |
上面这个例子中:
- 我们定义了一个
myFunc
函数,它会请求一个 url 并返回响应 - 我们定义了一个
TestHello
测试函数,它会启动一个 mock 服务,然后调用myFunc
函数,最后检查响应是否符合预期
它实现的功能是:我们的 myFunc
函数会请求 mock 服务的
/hello
路径,然后返回响应。
在实际开发中,myFunc
接收的 url
可能是一个外部服务的地址,比如第三方 api 的地址。
有了 httptest
,现在我们只需要替换 myFunc
中的 url 为 server.URL
,就可以实现对外部服务的 mock。
总结
在本文中,我们深入探讨了如何使用 Go 语言的
net/http/httptest
包来测试 HTTP 服务。我们首先介绍了 HTTP
测试的作用,包括测试自定义的 HTTP 接口以及模拟外部 HTTP
请求,以确保单元测试的稳定性和可控性。
接着,我们详细介绍了如何测试自定义的 HTTP 接口,通过使用
httptest.NewRecorder
来模拟
http.ResponseWriter
,从而捕获 HTTP
处理函数的输出。我们还展示了如何检查响应码和响应体,以验证处理函数的行为是否符合预期。
然后,我们讨论了如何模拟外部 HTTP 请求,通过定义一个
http.Handler
并使用 httptest.NewServer
来创建一个 mock HTTP
服务器。我们看到,通过这种方式,我们可以控制外部服务的响应,从而在测试环境中隔离外部依赖。