Testing applications that use net/http.Handler

3,685 views
Skip to first unread message

André Moraes

unread,
May 3, 2012, 8:34:36 AM5/3/12
to golan...@googlegroups.com
Is there any library which provide some functions on top of the "testing" and "net/http/httptest" packages to facilitate the testing of http.Handlers?

Things like Header checking, StatusCode checking, automatic Json Unmarshaling of the response body.

The current library gives enough flexiblity but for some simple tests the code becomes quite large.

Thanks in advance

Kyle Lemons

unread,
May 3, 2012, 1:13:02 PM5/3/12
to André Moraes, golan...@googlegroups.com
I use table-driven tests for this.  The test loop itself is somewhat long (doing the marshaling/unmarshaling, etc) but the tests are nicely separated from the implementation and easy to add/remove.  Unfortunately, it's nothing I can paste for you, since it deals with some of our peculiar wire protocols ;).

Volker Dobler

unread,
Nov 12, 2012, 5:08:28 AM11/12/12
to golan...@googlegroups.com
I once made something to facilitate end-to-end-testing: webtest [1].
It is currently not maintained but should build against r60 or so.
It is not a library but a standalone program which executes
test suites in a DSL. See the reference [2] what is possible in them.
Adding Json might be possible, I already thought a bit about
the test syntax.

It is unmaintained as nobody seems to like this type of tests:
They do not capture browser behavior like selenium but are
more complex to run than simple unit test. And it is not Java.

Regards, Volker

Stefano Charissis

unread,
Dec 29, 2012, 1:54:53 AM12/29/12
to golan...@googlegroups.com
I'm having trouble figuring out how to test handlers which are part of a method set of a struct (eg. My REST API). Every example of this I can find just places a test handler in the package_test.go. For example, from Andrew Gerrand's slideshow linked by haarts:

func testHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
}
func TestServer(t *testing.T) {
dummy := httptest.NewServer(http.HandlerFunc(testHandler))
...
}

But my handlers look like:

struct API {}
func (api *API) HandlerA(w http.ResponseWriter, req *http.Request) { ... }
func (api *API) HandlerB(w http.ResponseWriter, req *http.Request) { ... }
 
My attempts lead to this which compiles and runs but returns 0-ed values:

func Wrap(f func(_ *api.API, w http.ResponseWriter, r *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}
handler := http.HandlerFunc(Wrap((*api.API).HandlerA))

So how does one access a handler like so? Presumably this is a more realistic scenario? I don't understand why peoples examples place a handler in the test code...

Many thanks in advance!

Kyle Lemons

unread,
Dec 29, 2012, 4:24:02 AM12/29/12
to Stefano Charissis, golang-nuts
It all depends what you're testing.  If you're testing your handlers themselves, I'd do something like this.


func TestHandlers(t *testing.T) {
tests := []struct {
Desc    string
Handler func(Example, http.ResponseWriter, *http.Request)
Path    string
Params  url.Values
Status  int
Match   map[string]bool
}{{
Desc:    "basic",
Handler: Example.HandleOne,
Path:    "/world",
Status:  http.StatusOK,
Match: map[string]bool{
"Hello": true,
"world": true,
},
}, {
Desc:    "basic",
Handler: Example.HandleTwo,
Path:    "/world",
Params: url.Values{
"name": {"gopher"},
},
Status: http.StatusOK,
Match: map[string]bool{
"Hello":  true,
"world":  false,
"gopher": true,
},
}}

for _, test := range tests {
record := httptest.NewRecorder()
req := &http.Request{
Method: "GET",
URL:    &url.URL{Path: test.Path},
Form:   test.Params,
}
test.Handler(Example{}, record, req)
if got, want := record.Code, test.Status; got != want {
t.Errorf("%s: response code = %d, want %d", test.Desc, got, want)
}
for re, match := range test.Match {
if got := regexp.MustCompile(re).Match(record.Body.Bytes()); got != match {
t.Errorf("%s: %q ~ /%s/ = %v, want %v", test.Desc, record.Body, re, got, match)
}
}
}
}



--
 
 

Stefano Charissis

unread,
Dec 29, 2012, 6:26:58 PM12/29/12
to golan...@googlegroups.com, Stefano Charissis
Kyle, thanks a million! That worked like a charm.

Turns out that part of my issue was due to the fact that I had implemented my interface  a bit wrong:

Instead of: 
func (e Example) HandleOne(w http.ResponseWriter, r *http.Request)

I had:
func (e *Example) HandleOne(w http.ResponseWriter, r *http.Request)

I'm not sure what the difference is, but I couldn't manage to cast it in the pointer form.

But, also, thank you for providing me with that nice skeleton for handler testing.

Cheers!

Francisco Souza

unread,
Dec 29, 2012, 8:34:07 PM12/29/12
to Stefano Charissis, golang-nuts
On Sat, Dec 29, 2012 at 9:26 PM, Stefano Charissis
<stefan...@gmail.com> wrote:
> Kyle, thanks a million! That worked like a charm.
>
> Turns out that part of my issue was due to the fact that I had implemented
> my interface a bit wrong:
>
> Instead of:
> func (e Example) HandleOne(w http.ResponseWriter, r *http.Request)
>
> I had:
> func (e *Example) HandleOne(w http.ResponseWriter, r *http.Request)
>
> I'm not sure what the difference is, but I couldn't manage to cast it in the
> pointer form.

http://golang.org/doc/go_faq.html#methods_on_values_or_pointers

http://golang.org/doc/go_faq.html#different_method_sets


--
~f

Stefano Charissis

unread,
Dec 29, 2012, 9:45:08 PM12/29/12
to golan...@googlegroups.com, Stefano Charissis
Thank you Francisco, that explains it.
Reply all
Reply to author
Forward
0 new messages