Testing Hapi Plugins

Garth Henson
JavaScript, Node.js
4 Nov 2016

Recently, I have found myself needing to build out some authorization modules for both Hapi and Express applications. Hapi has become my server of choice, and building some fully testable plugins has been a priority. In the process, I have learned several techniques that have really streamlined my testing.

There are a few tools I’ve settled on for the baseline structure of my testing:

Test runner and base framework
Chai (Chai.expect)
Assertion library
Mocks, Stubs, Spies and many other useful helpers
Simplifies assertions against explicit routes in our server
Mocks Node’s HTTP request library for injection of responses

The Plugin

For our use case, we will look at an authorization plugin that examines inbound request headers for an Authorization token – using the hapi-auth-header module. If the token exists, the plugin will leverage a custom library to validate the token against our Oauth service. Here is an extremely simple example of our plugin:

With the plugin, we have registered a new authorization strategy and have instructed the server to apply this strategy to all routes by default. If any requests come in without an Authorization header containing a bearer token, or if our Oauth library rejects the validation promise, the server will send a 401 Unauthorized response.

Now, if we want to expose specific endpoints on our service that do not require authentication, such as health checks, we can do so with the config option on those routes:

If we configure the plugin not to apply our custom authorization by default, we can use the same config property to apply our authorization by route as well. Since we have a newly named strategy, this will enable it for a route:

After some manual checks to verify things are working as expected, we need to write some automated tests against our plugin.

Plugin Tests

Without getting into the nuances of testing strategies, the previous example has essentially three major paths that need to be tested: one happy path and two failure cases (nonexistent auth token and invalid auth token). Since we want to be testing in a sandboxed environment, we will have to stub some of the underlying library methods that our plugin is using, namely the oauth.validate() call.

Setup for Success

Thorough testing doesn’t ever happen by accident! We actually need to design our code to be testable. Notice the following code from our plugin above:

Only when our plugin is initialized with an environment option set to “testing”, we expose the underlying Oauth library in order to stub it out for thorough testing. In Hapi, when you use the expose() method like this, you can now access the exposed property within the plugin attribute on the server itself. In our test suite, we will set up the server with this environment set:

Notice what we have done is set the stage for all our testing. We have a route at /protected that will apply our new authentication plugin to the request headers, a second route at /open that will not, and both routes will reply with a simple “ok” string when they have allowed access.

Writing the Tests

First, take note that we will be using supertest to make all our requests directly on our server. Be sure to use this module in your test suite:

Now, we need to set up our tests for all three of our main scenarios. This is where a really cool Mocha feature comes in handy: Promise mocking. Rather than having to jump through a lot of hoops to test this, we can simply stub out the oauth.validate() call, and we have full control over our scenarios:

Incredibly simple, no? This tests only for the existence of auth headers and responds appropriately for both our configured routes. Now, we get just a bit more complex. Remember when we set up our plugin to expose the Oauth object to be mocked? Now is when we hook into that and test both resolution and rejection cases:

Mocha gives us an amazing boon here: the ability to create new Promise objects in a resolved or rejected state that we can then stub or inject into our different methods. I began this exercise by trying to mock the bluebird library, but there are implicit exceptions that will be thrown in failure cases, even if your tests fully pass. By using Mocha’s promises, we can write pretty robust tests in just a few lines of code!

At this point, we have covered our three major use cases without having to reach out to any external sources. To be able to claim full code coverage, though, we should write some mocked tests around our oauth library as well, but we will cover that in another post.

Hopefully this will help save someone else some of the time it took me to discover the different techniques.

Garth Henson
Garth is as a lead engineer at The Walt Disney Company, specializing in JavaScript applications.

Recent Blog Posts

Let's Work Together
Contact Me