Skip to content

Commit

Permalink
net/http/httptest: add NewRequest helper for ease of testing handlers
Browse files Browse the repository at this point in the history
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
bradfitz committed Mar 23, 2016
1 parent bd83cc6 commit 694eadc
Show file tree
Hide file tree
Showing 3 changed files with 265 additions and 1 deletion.
88 changes: 88 additions & 0 deletions src/net/http/httptest/httptest.go
@@ -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
}
177 changes: 177 additions & 0 deletions src/net/http/httptest/httptest_test.go
@@ -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)
}
}
}
1 change: 0 additions & 1 deletion src/net/http/httptest/recorder.go
Expand Up @@ -2,7 +2,6 @@
// 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 (
Expand Down

0 comments on commit 694eadc

Please sign in to comment.