Skip to content

Commit e5938cc

Browse files
committed
[docs] Add testing example
1 parent 53c1911 commit e5938cc

File tree

1 file changed

+132
-7
lines changed

1 file changed

+132
-7
lines changed

README.md

Lines changed: 132 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
gorilla/mux
2-
===
1+
# gorilla/mux
2+
33
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
44
[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
55
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
@@ -29,6 +29,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
2929
* [Walking Routes](#walking-routes)
3030
* [Graceful Shutdown](#graceful-shutdown)
3131
* [Middleware](#middleware)
32+
* [Testing Handlers](#testing-handlers)
3233
* [Full Example](#full-example)
3334

3435
---
@@ -178,6 +179,7 @@ s.HandleFunc("/{key}/", ProductHandler)
178179
// "/products/{key}/details"
179180
s.HandleFunc("/{key}/details", ProductDetailsHandler)
180181
```
182+
181183
### Listing Routes
182184

183185
Routes on a mux can be listed using the Router.Walk method—useful for generating documentation:
@@ -241,7 +243,7 @@ func main() {
241243

242244
Note that the path provided to `PathPrefix()` represents a "wildcard": calling
243245
`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
244-
request that matches "/static/*". This makes it easy to serve static files with mux:
246+
request that matches "/static/\*". This makes it easy to serve static files with mux:
245247

246248
```go
247249
func main() {
@@ -410,7 +412,7 @@ func main() {
410412

411413
r := mux.NewRouter()
412414
// Add your routes as needed
413-
415+
414416
srv := &http.Server{
415417
Addr: "0.0.0.0:8080",
416418
// Good practice to set timeouts to avoid Slowloris attacks.
@@ -426,7 +428,7 @@ func main() {
426428
log.Println(err)
427429
}
428430
}()
429-
431+
430432
c := make(chan os.Signal, 1)
431433
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
432434
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
@@ -502,7 +504,7 @@ func (amw *authenticationMiddleware) Populate() {
502504
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
503505
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
504506
token := r.Header.Get("X-Session-Token")
505-
507+
506508
if user, found := amw.tokenUsers[token]; found {
507509
// We found the token in our map
508510
log.Printf("Authenticated user %s\n", user)
@@ -526,7 +528,130 @@ amw.Populate()
526528
r.AddMiddlewareFunc(amw.Middleware)
527529
```
528530

529-
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares *should* write to `ResponseWriter` if they *are* going to terminate the request, and they *should not* write to `ResponseWriter` if they *are not* going to terminate it.
531+
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it.
532+
533+
### Testing Handlers
534+
535+
Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_.
536+
537+
First, our simple HTTP handler:
538+
539+
```go
540+
// endpoints.go
541+
package main
542+
543+
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
544+
// A very simple health check.
545+
w.WriteHeader(http.StatusOK)
546+
w.Header().Set("Content-Type", "application/json")
547+
548+
// In the future we could report back on the status of our DB, or our cache
549+
// (e.g. Redis) by performing a simple PING, and include them in the response.
550+
io.WriteString(w, `{"alive": true}`)
551+
}
552+
553+
func main() {
554+
r := mux.NewRouter()
555+
r.HandleFunc("/health", HealthCheckHandler)
556+
557+
log.Fatal(http.ListenAndServe("localhost:8080", r))
558+
}
559+
```
560+
561+
Our test code:
562+
563+
```go
564+
// endpoints_test.go
565+
package main
566+
567+
import (
568+
"net/http"
569+
"net/http/httptest"
570+
"testing"
571+
)
572+
573+
func TestHealthCheckHandler(t *testing.T) {
574+
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
575+
// pass 'nil' as the third parameter.
576+
req, err := http.NewRequest("GET", "/health", nil)
577+
if err != nil {
578+
t.Fatal(err)
579+
}
580+
581+
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
582+
rr := httptest.NewRecorder()
583+
handler := http.HandlerFunc(HealthCheckHandler)
584+
585+
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
586+
// directly and pass in our Request and ResponseRecorder.
587+
handler.ServeHTTP(rr, req)
588+
589+
// Check the status code is what we expect.
590+
if status := rr.Code; status != http.StatusOK {
591+
t.Errorf("handler returned wrong status code: got %v want %v",
592+
status, http.StatusOK)
593+
}
594+
595+
// Check the response body is what we expect.
596+
expected := `{"alive": true}`
597+
if rr.Body.String() != expected {
598+
t.Errorf("handler returned unexpected body: got %v want %v",
599+
rr.Body.String(), expected)
600+
}
601+
}
602+
```
603+
604+
In the case that our routes have [variables](#examples), we can pass those in the request. We could write
605+
[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple
606+
possible route variables as needed.
607+
608+
```go
609+
// endpoints.go
610+
func main() {
611+
r := mux.NewRouter()
612+
// A route with a route variable:
613+
r.HandleFunc("/metrics/{type}", MetricsHandler)
614+
615+
log.Fatal(http.ListenAndServe("localhost:8080", r))
616+
}
617+
```
618+
619+
Our test file, with a table-driven test of `routeVariables`:
620+
621+
```go
622+
// endpoints_test.go
623+
func TestMetricsHandler(t *testing.T) {
624+
tt := []struct{
625+
routeVariable string
626+
shouldPass bool
627+
}{
628+
{"goroutines", true},
629+
{"heap", true},
630+
{"counters", true},
631+
{"queries", true},
632+
{"adhadaeqm3k", false},
633+
}
634+
635+
for _, t := tt {
636+
path := fmt.Sprintf("/metrics/%s", t.routeVariable)
637+
req, err := http.NewRequest("GET", path, nil)
638+
if err != nil {
639+
t.Fatal(err)
640+
}
641+
642+
rr := httptest.NewRecorder()
643+
handler := http.HandlerFunc(MetricsHandler)
644+
handler.ServeHTTP(rr, req)
645+
646+
// In this case, our MetricsHandler returns a non-200 response
647+
// for a route variable it doesn't know about.
648+
if rr.Code == http.StatusOK && !t.shouldPass {
649+
t.Errorf("handler should have failed on routeVariable %s: got %v want %v",
650+
t.routeVariable, rr.Code, http.StatusOK)
651+
}
652+
}
653+
}
654+
```
530655

531656
## Full Example
532657

0 commit comments

Comments
 (0)