Testing Go Http Handlers in Google App Engine with Mux and Higher Order Functions

For those people that aren’t familiar to building applications with Go and Google App Engine, the core data structure that is used whenever you need to make a request to any of the provided Google App Engine Services, is a Context.

Usually, this is created by passing in the http.Request object that is created when serving a Http request, however, when you want to automate the testing of your Http Request Handlers, you usually do something like the following:

You create your own Request with the values you want to test, attempt to create a Context from it…. and blammo, GAE panics because the Request wasn’t sent through the actual GAE development server.

There are a few ways to solve this problem, but this is the way I found worked best for the situation I had. I’m using Mux for doing my routing (which is a great library), which provides me with a http.Handler to server all my requests through. My first (naive) solution, was to use (another great) library Context, which enables you to attach data to the running Request.  Which meant my Handler function ended up looking something like this:

In my my tests I could create a Context with aetest, which creates a test Context for you, and attach it to the request for my Handler function to find along the way.

This didn’t feel like a good solution, and would mean that each of my http handler functions would be peppered with this boilerplate check to see there was a context or not, which I wasn’t happy with. As looked at before, I could have wrapped Mux with my a custom http.Handler, which would have worked, but given my recent proclivity for functional programming, I leaned more towards solving this problem by manipulating functions, than creating objects with state.

The first thing I did was define three types, to make writing out my higher order functions easier:

The first type HandleFunc is simply a convenience type for our usual http Handler function signature. The second type ContextHandlerFunc is a type that has the function signature of what I want my http Handlers to look like when I magically have an appengine.Context available. Finally I have a ContextHandlerToHandlerHOF, which gives me the the function signature that I will need to take in a ContextHandlerFunc and convert it into a HandleFunc, so that it can be used with regular http routing APIs.

Therefore, for my application code, I have this function below, which takes in the ContextHandlerFunc, and returns a function that matches the HandleFunc signature, which, when invoked, will create a new appengine.Context and pass it through to my ContextHandlerFunc.

I then have a second function called CreateHandler. It’s job it to create the mux.Router. As an argument it takes in a ContextHandlerToHandlerHOF, whose job it is to make the conversion to a standard HandleFunc() format. This means we can change how the appengine.Context gets created by passing in different ContextHandlerToHandlerHOF implementations.  In this case, our init() function uses the one we want to use for our production code, that we defined above.

This means that for my tests, I have to get a bit more creative, because I need access to my aetest.Context outside of when I create my handler, mainly because in my tests it’s very important to Close() it after you are done.

So below, you can see CreateContextHandlerToHttpHandler, which creates a ContextHandlerToHandlerHOF, with the appengine.Context that is being provided, and rather than creating a new Context like in production, it simply uses the one provided.

Now I don’t get a panic from my local Google App Engine Development server when I run my tests, as I can easily switch out how the appengine.Context is created, depending on what environment the code is running in.

I’ve also found I’ve been able able to also extend this approach, to use functional composition for my middleware layer as well (another post for another time). All in all, I’m very happy that Go has first class functions!

Leave a Comment

Comments

  • Kyle | February 20, 2015

    This is the post that brought it all together. Thanks!