Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
net/http/httptest: add NewRequest helper for ease of testing handlers
Fixes #14199 Change-Id: Ic9284023b663de3db1ca7b7b1e96eeab82ec0944 Reviewed-on: https://go-review.googlesource.com/21016 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> Reviewed-by: Andrew Gerrand <adg@golang.org>
- Loading branch information
Showing
3 changed files
with
265 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// Copyright 2016 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// Package httptest provides utilities for HTTP testing. | ||
package httptest | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"crypto/tls" | ||
"io" | ||
"io/ioutil" | ||
"net/http" | ||
"strings" | ||
) | ||
|
||
// NewRequest returns a new incoming server Request, suitable | ||
// for passing to an http.Handler for testing. | ||
// | ||
// The target is the RFC 7230 "request-target": it may be either a | ||
// path or an absolute URL. If target is an absolute URL, the host name | ||
// from the URL is used. Otherwise, "example.com" is used. | ||
// | ||
// The TLS field is set to a non-nil dummy value if target has scheme | ||
// "https". | ||
// | ||
// The Request.Proto is always HTTP/1.1. | ||
// | ||
// An empty method means "GET". | ||
// | ||
// The provided body may be nil. If the body is of type *bytes.Reader, | ||
// *strings.Reader, or *bytes.Buffer, the Request.ContentLength is | ||
// set. | ||
// | ||
// NewRequest panics on error for ease of use in testing, where a | ||
// panic is acceptable. | ||
func NewRequest(method, target string, body io.Reader) *http.Request { | ||
if method == "" { | ||
method = "GET" | ||
} | ||
req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(method + " " + target + " HTTP/1.0\r\n\r\n"))) | ||
if err != nil { | ||
panic("invalid NewRequest arguments; " + err.Error()) | ||
} | ||
|
||
// HTTP/1.0 was used above to avoid needing a Host field. Change it to 1.1 here. | ||
req.Proto = "HTTP/1.1" | ||
req.ProtoMinor = 1 | ||
req.Close = false | ||
|
||
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 = ioutil.NopCloser(body) | ||
} | ||
} | ||
|
||
// 192.0.2.0/24 is "TEST-NET" in RFC 5737 for use solely in | ||
// documentation and example source code and should not be | ||
// used publicly. | ||
req.RemoteAddr = "192.0.2.1:1234" | ||
|
||
if req.Host == "" { | ||
req.Host = "example.com" | ||
} | ||
|
||
if strings.HasPrefix(target, "https://") { | ||
req.TLS = &tls.ConnectionState{ | ||
Version: tls.VersionTLS12, | ||
HandshakeComplete: true, | ||
ServerName: req.Host, | ||
} | ||
} | ||
|
||
return req | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
// Copyright 2016 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package httptest | ||
|
||
import ( | ||
"crypto/tls" | ||
"io" | ||
"io/ioutil" | ||
"net/http" | ||
"net/url" | ||
"reflect" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestNewRequest(t *testing.T) { | ||
tests := [...]struct { | ||
method, uri string | ||
body io.Reader | ||
|
||
want *http.Request | ||
wantBody string | ||
}{ | ||
// Empty method means GET: | ||
0: { | ||
method: "", | ||
uri: "/", | ||
body: nil, | ||
want: &http.Request{ | ||
Method: "GET", | ||
Host: "example.com", | ||
URL: &url.URL{Path: "/"}, | ||
Header: http.Header{}, | ||
Proto: "HTTP/1.1", | ||
ProtoMajor: 1, | ||
ProtoMinor: 1, | ||
RemoteAddr: "192.0.2.1:1234", | ||
RequestURI: "/", | ||
}, | ||
wantBody: "", | ||
}, | ||
|
||
// GET with full URL: | ||
1: { | ||
method: "GET", | ||
uri: "http://foo.com/path/%2f/bar/", | ||
body: nil, | ||
want: &http.Request{ | ||
Method: "GET", | ||
Host: "foo.com", | ||
URL: &url.URL{ | ||
Scheme: "http", | ||
Path: "/path///bar/", | ||
RawPath: "/path/%2f/bar/", | ||
Host: "foo.com", | ||
}, | ||
Header: http.Header{}, | ||
Proto: "HTTP/1.1", | ||
ProtoMajor: 1, | ||
ProtoMinor: 1, | ||
RemoteAddr: "192.0.2.1:1234", | ||
RequestURI: "http://foo.com/path/%2f/bar/", | ||
}, | ||
wantBody: "", | ||
}, | ||
|
||
// GET with full https URL: | ||
2: { | ||
method: "GET", | ||
uri: "https://foo.com/path/", | ||
body: nil, | ||
want: &http.Request{ | ||
Method: "GET", | ||
Host: "foo.com", | ||
URL: &url.URL{ | ||
Scheme: "https", | ||
Path: "/path/", | ||
Host: "foo.com", | ||
}, | ||
Header: http.Header{}, | ||
Proto: "HTTP/1.1", | ||
ProtoMajor: 1, | ||
ProtoMinor: 1, | ||
RemoteAddr: "192.0.2.1:1234", | ||
RequestURI: "https://foo.com/path/", | ||
TLS: &tls.ConnectionState{ | ||
Version: tls.VersionTLS12, | ||
HandshakeComplete: true, | ||
ServerName: "foo.com", | ||
}, | ||
}, | ||
wantBody: "", | ||
}, | ||
|
||
// Post with known length | ||
3: { | ||
method: "POST", | ||
uri: "/", | ||
body: strings.NewReader("foo"), | ||
want: &http.Request{ | ||
Method: "POST", | ||
Host: "example.com", | ||
URL: &url.URL{Path: "/"}, | ||
Header: http.Header{}, | ||
Proto: "HTTP/1.1", | ||
ContentLength: 3, | ||
ProtoMajor: 1, | ||
ProtoMinor: 1, | ||
RemoteAddr: "192.0.2.1:1234", | ||
RequestURI: "/", | ||
}, | ||
wantBody: "foo", | ||
}, | ||
|
||
// Post with unknown length | ||
4: { | ||
method: "POST", | ||
uri: "/", | ||
body: struct{ io.Reader }{strings.NewReader("foo")}, | ||
want: &http.Request{ | ||
Method: "POST", | ||
Host: "example.com", | ||
URL: &url.URL{Path: "/"}, | ||
Header: http.Header{}, | ||
Proto: "HTTP/1.1", | ||
ContentLength: -1, | ||
ProtoMajor: 1, | ||
ProtoMinor: 1, | ||
RemoteAddr: "192.0.2.1:1234", | ||
RequestURI: "/", | ||
}, | ||
wantBody: "foo", | ||
}, | ||
|
||
// OPTIONS * | ||
5: { | ||
method: "OPTIONS", | ||
uri: "*", | ||
want: &http.Request{ | ||
Method: "OPTIONS", | ||
Host: "example.com", | ||
URL: &url.URL{Path: "*"}, | ||
Header: http.Header{}, | ||
Proto: "HTTP/1.1", | ||
ProtoMajor: 1, | ||
ProtoMinor: 1, | ||
RemoteAddr: "192.0.2.1:1234", | ||
RequestURI: "*", | ||
}, | ||
}, | ||
} | ||
for i, tt := range tests { | ||
got := NewRequest(tt.method, tt.uri, tt.body) | ||
slurp, err := ioutil.ReadAll(got.Body) | ||
if err != nil { | ||
t.Errorf("%i. ReadAll: %v", i, err) | ||
} | ||
if string(slurp) != tt.wantBody { | ||
t.Errorf("%i. Body = %q; want %q", i, slurp, tt.wantBody) | ||
} | ||
got.Body = nil // before DeepEqual | ||
if !reflect.DeepEqual(got.URL, tt.want.URL) { | ||
t.Errorf("%d. Request.URL mismatch:\n got: %#v\nwant: %#v", i, got.URL, tt.want.URL) | ||
} | ||
if !reflect.DeepEqual(got.Header, tt.want.Header) { | ||
t.Errorf("%d. Request.Header mismatch:\n got: %#v\nwant: %#v", i, got.Header, tt.want.Header) | ||
} | ||
if !reflect.DeepEqual(got.TLS, tt.want.TLS) { | ||
t.Errorf("%d. Request.TLS mismatch:\n got: %#v\nwant: %#v", i, got.TLS, tt.want.TLS) | ||
} | ||
if !reflect.DeepEqual(got, tt.want) { | ||
t.Errorf("%d. Request mismatch:\n got: %#v\nwant: %#v", i, got, tt.want) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters