httptest是golang官方源码自带的测试包,它可以非常方便获取http请求结构体,http返回值结构体,以及在本地启动一个loopback的server,方便我们做单测。对于go的web应用程序中往往需要与其他系统进行交互, 比如通过http访问其他系统, 此时就需要一种方法用于打桩来模拟Web服务端和客户端,httptest包即Go语言针对Web应用提供的解决方案。
1,获取返回值
rr := httptest.NewRecorder()
我们创建一个 ResponseRecorder (which satisfies http.ResponseWriter)来记录响应,主要利用httptest.NewRecorder()创建一个http.ResponseWriter,模拟了真实服务端的响应,这种响应时通过调用http.DefaultServeMux.ServeHTTP方法触发的。对应源码位于src/net/http/httptest/recorder.go
type ResponseRecorder struct {
// Code is the HTTP response code set by WriteHeader.
//
// Note that if a Handler never calls WriteHeader or Write,
// this might end up being 0, rather than the implicit
// http.StatusOK. To get the implicit value, use the Result
// method.
Code int
// HeaderMap contains the headers explicitly set by the Handler.
// It is an internal detail.
//
// Deprecated: HeaderMap exists for historical compatibility
// and should not be used. To access the headers returned by a handler,
// use the Response.Header map as returned by the Result method.
HeaderMap http.Header
// Body is the buffer to which the Handler's Write calls are sent.
// If nil, the Writes are silently discarded.
Body *bytes.Buffer
// Flushed is whether the Handler called Flush.
Flushed bool
result *http.Response // cache of Result's return value
snapHeader http.Header // snapshot of HeaderMap at first Write
wroteHeader bool
}
func NewRecorder() *ResponseRecorder {
return &ResponseRecorder{
HeaderMap: make(http.Header),
Body: new(bytes.Buffer),
Code: 200,
}
}
它对应的方法如下,可以根据实际需求进行调用。
func (rw *ResponseRecorder) Header() http.Header {
func (rw *ResponseRecorder) writeHeader(b []byte, str string) {
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
func (rw *ResponseRecorder) WriteString(str string) (int, error) {
func (rw *ResponseRecorder) WriteHeader(code int) {
func (rw *ResponseRecorder) Flush() {
func (rw *ResponseRecorder) Result() *http.Response {
2,获取http请求
req := httptest.NewRequest(
http.MethodPost,
"/health-check",
bytes.NewReader(reqBody),
)
在测试的时候可以把http.NewRequest替换为httptest.NewRequest。httptest.NewRequest的第三个参数可以用来传递body数据,必须实现io.Reader接口。httptest.NewRequest不会返回error,无需进行err!=nil检查。解析响应时没直接使用ResponseRecorder,而是调用了Result函数。源码位于src/net/http/httptest/httptest.go
func NewRequest(method, target string, body io.Reader) *http.Request {
req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(method + " " + target + " HTTP/1.0\r\n\r\n")))
if body != nil {
switch v := body.(type) {
case *bytes.Buffer:
req.ContentLength = int64(v.Len())
case *bytes.Reader:
req.ContentLength = int64(v.Len())
case *strings.Reader:
req.ContentLength = int64(v.Len())
default:
req.ContentLength = -1
}
if rc, ok := body.(io.ReadCloser); ok {
req.Body = rc
} else {
req.Body = io.NopCloser(body)
}
}
3,本地起模拟服务器
httptest.NewServer(http.HandlerFunc(healthHandler))
模拟服务器的创建使用的是httptest.NewServer函数,它接收一个http.Handler处理API请求的接口。代码示例中使用了Hander的适配器模式,http.HandlerFunc是一个函数类型,实现了http.Handler接口,这里是强制类型转换,不是函数的调用,源码位于:src/net/http/httptest/server.go
type Server struct {
URL string // base URL of form http://ipaddr:port with no trailing slash
Listener net.Listener
// EnableHTTP2 controls whether HTTP/2 is enabled
// on the server. It must be set between calling
// NewUnstartedServer and calling Server.StartTLS.
EnableHTTP2 bool
// TLS is the optional TLS configuration, populated with a new config
// after TLS is started. If set on an unstarted server before StartTLS
// is called, existing fields are copied into the new config.
TLS *tls.Config
// Config may be changed after calling NewUnstartedServer and
// before Start or StartTLS.
Config *http.Server
// certificate is a parsed version of the TLS config certificate, if present.
certificate *x509.Certificate
// wg counts the number of outstanding HTTP requests on this server.
// Close blocks until all requests are finished.
wg sync.WaitGroup
mu sync.Mutex // guards closed and conns
closed bool
conns map[net.Conn]http.ConnState // except terminal states
// client is configured for use with the server.
// Its transport is automatically closed when Close is called.
client *http.Client
}
默认会监听127.0.0.1:0
func newLocalListener() net.Listener {
if serveFlag != "" {
l, err := net.Listen("tcp", serveFlag)
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
func NewServer(handler http.Handler) *Server {
ts := NewUnstartedServer(handler)
ts.Start()
return ts
}
func NewUnstartedServer(handler http.Handler) *Server {
return &Server{
Listener: newLocalListener(),
Config: &http.Server{Handler: handler},
}
}
func (s *Server) Start() {
s.URL = "http://" + s.Listener.Addr().String()
s.wrap()
s.goServe()
func (s *Server) StartTLS() {
s.Listener = tls.NewListener(s.Listener, s.TLS)
s.URL = "https://" + s.Listener.Addr().String()
s.wrap()
s.goServe()
func NewTLSServer(handler http.Handler) *Server {
ts := NewUnstartedServer(handler)
ts.StartTLS()
return ts
}
func (s *Server) Close() {
for c, st := range s.conns {
if st == http.StateIdle || st == http.StateNew {
s.closeConn(c)
}
func (s *Server) CloseClientConnections() {
for c := range s.conns {
go s.closeConnChan(c, ch)
}
func (s *Server) goServe() {
s.wg.Add(1)
go func() {
defer s.wg.Done()
s.Config.Serve(s.Listener)
}()
}
在协程里对不同http状态进行不同处理
func (s *Server) wrap() {
s.Config.ConnState = func(c net.Conn, cs http.ConnState) {
switch cs {
case http.StateNew:
case http.StateActive:
case http.StateIdle:
case http.StateHijacked, http.StateClosed: