httptest and gorilla/mux route variables

7038 views
Skip to first unread message

ps

unread,
Oct 3, 2013, 5:08:22 PM10/3/13
to golan...@googlegroups.com
Hello, does anybody have a good workaround for the issue of testing gorilla/mux routes that contain variables with httptest? (There's an old discussion about this on the gorilla group, but it seems dead over there.)

Basically, if you test something with a request like the following, the variable (1, in this case) isn't captured:

http.NewRequest("GET", "http://localhost:3000/employees/1", nil)

At runtime, during normal execution, the variable is captured and a browser/curl request returns a successful response. However while testing, only an empty map is returned, without the "id" variable.

Alternatively, has anybody written a simple api router with similar functionality, perhaps without regexes for improved performance (numerous searches came up empty)? I've been considering doing that, even if it seems like a fool's errand.

Kyle Lemons

unread,
Oct 3, 2013, 5:10:55 PM10/3/13
to ps, golang-nuts
Show some code?  Maybe I'm missing something, but I don't see why it might work in real life but not in testing.


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Brad Rydzewski

unread,
Oct 4, 2013, 11:55:32 AM10/4/13
to golan...@googlegroups.com
Are you trying to invoke the handler directly in your unit test? like this:

func HandlerTest(t *testing.T) {
r := http.NewRequest("GET", "http://localhost:3000/employees/1", nil)
w := httptest.NewRecorder()

myhandler(r, w)
...
}

If you dig into the Gorialla source code the URL parameters are parsed by the router:
https://github.com/gorilla/mux/blob/master/mux.go#L78

and these parameters are stored inside a global map inside the context package, where the http.Request is the key:

so if you have this code in your handler
vars := mux.Vars(req)
id := vars["id"]

It assumes the router has parsed the URL parameters and set the request / parameters in the global map. If you are invoking the handler directly in a unit test, and bypassing the router, that would explain why your variables are blank.

Maybe as part of your unit test, you can set the request and parameters manually:
func HandlerTest(t *testing.T) {
r := http.NewRequest("GET", "http://localhost:3000/employees/1", nil)
w := httptest.NewRecorder()
context.Set(r, 0, 1)
myhandler(r, w)
...
}

where in the above code, 0 is the key used by gorilla for storing URL parameters (I think) and 1 is the ID of you employee.
Message has been deleted

Brad Rydzewski

unread,
Oct 4, 2013, 12:13:46 PM10/4/13
to golan...@googlegroups.com
Edit to my previous post:

I suggested the parameters could be set manually like this:
context.Set(r, 0, 1)

This is incorrect, the parameters need to be set by passing in a RouteMatch:
context.Set(r, 0, &mux.RouteMatch{ ... })

which looks like this:
type RouteMatch struct {
Route   *Route
Handler http.Handler
Vars    map[string]string
}

Creating the RouteMatch looks like it might be a bit tricky since the *Route is comprised of all private fields. So invoking context.Set directly is probably not an option. The only other option I can think of is, in your unit test, construct a router and send your request through the entire Gorialla request lifecycle. Like this:

func HandlerTest(t *testing.T) {
r := http.NewRequest("GET", "http://localhost:3000/employees/1", nil)
w := httptest.NewRecorder()

m := mux.NewRouter()
m.HandleFunc("/employees/{id}", myhandler)
m.ServeHTTP(w, r)

...
}

Kamil Kisiel

unread,
Oct 4, 2013, 12:31:17 PM10/4/13
to golan...@googlegroups.com
That's definitely one way of doing it. It's also possible to construct your handlers in such a way as to have the vars passed in as an argument instead of requiring a call to mux.Vars. This requires rejigging your app a little bit:

type VarsHandler func(http.ResponseWriter, *http.Request, map[string]string)

func (h VarsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    vars := mux.Vars(req)
    h(w, req, vars)
}

func main() {
    router := mux.NewRouter()
    router.Handle("/employees/{id}", VarsHandler(myHandler))
    ...
}

Now you can test myHandler by simply calling it like:

myHandler(w, req, map[string]string{"id": "1234"})

ps

unread,
Oct 4, 2013, 11:48:19 PM10/4/13
to golan...@googlegroups.com
Thanks Brad and Kamil for your help! I'm passing the vars as described and it's working great so far.

Brad, what you're describing was part of the discussion on the mailing list (it appears that the context has been exposed and unexposed during the evolution of the project, and is still being considered).
Message has been deleted

Thomas Bruyelle

unread,
Aug 25, 2014, 3:21:08 AM8/25/14
to golan...@googlegroups.com
I found a solution that doesn't require you to manually add the mux Vars in your tests or use the mux Context.

The key is to create your mux router in a separate function like that

func Router() *mux.Router {
  r := mux.Router()
  r.HandleFunc("/employees/{1}", employeeHandler)
  (...)
  return r
}

func init() {
http.Handle("/", Router())
}


Then in your tests, you can use directly the Router(), just like that 

func TestEmployeeHandler(t *testing.T) {
  r := http.NewRequest("GET", "employees/1", nil)
  w := httptest.NewRecorder()

 Router().ServeHTTP(w, r)
  ... 
}

emmanuel...@crowdint.com

unread,
Jan 25, 2015, 2:25:26 PM1/25/15
to golan...@googlegroups.com
Thank you for the example, just used it in this example code to test my routes written with https://github.com/julienschmidt/httprouter.

Best,

Emmanuel D

al...@getloopd.com

unread,
May 12, 2015, 10:04:21 AM5/12/15
to golan...@googlegroups.com
Awesome! This works great Thomas :)

Kevin Mathew S

unread,
Dec 3, 2021, 6:29:57 PM12/3/21
to golang-nuts
This works perfectly for me

func newReq(method, path, body string, vars map[string]string) *http.Request {
r := httptest.NewRequest(method, path, strings.NewReader(body))
return mux.SetURLVars(r, vars)
}


Reply all
Reply to author
Forward
0 new messages