15 Sep 2020 - Melanie Dworak
Routed HTTP requests are a key element of any Hapi application. Unfortunately, the request/response payloads that Hapi automatically generates cannot be intuitively stubbed out. As a result, it’s difficult to manually write inputs for our unit tests.
Our remaining options are to actually send an HTTP request through our routes (defeating the purpose of this exercise!) or to have Hapi imitate a routed method call using its built-in server.inject().
server.inject
You’ll need to instantiate a new Hapi server. Just stick this with the rest of your declarations at the top of your test file:
const testServer = new Hapi.Server();
Don’t worry about starting the server or anything else — we just need it to exist!
Next, we need to create the route[s] that will handle our HTTP requests. Depending on your program structure, it’s possible to simply import your production routes and bind them to the test server. If this will not be possible for you, feel free to write a truncated version of the actual route associated with the HTTP requests. A truncated route should at minimum look something like:
testServer.route({
method: ‘POST’,
path: ‘/requestRoute’,
options: {
handler: methodToTest()
}
});
Don’t be afraid to include your validators, tags, plugins, or any other route feature you would like to test! The best practice will always be to use the actual production route, but if that’s not possible try to get as close as you can to the real thing!
We call server.inject()
to put our HTTP request through and get Hapi’s returned ResponseObject
.
server.inject
can be called two different ways. The first:
server.inject(‘request-path’);
This version of server.inject()
puts through a basic GET request to the given request-path. You likely won’t need to use this version much, given its inflexibility.
The second way to call server.inject()
(and the one you probably want):
server.inject(injectOptions);
We define injectOptions separately; it allows us to customize the request in whatever way we might need. It’s usually a good practice to define a request’s injectOptions
within each test.
injectOptions
injectOptions
will define the specifics of server.inject()
’s HTTP request. Here’s an injectOptions
template:
const injectOptions = {
method: ‘HTTP REQUEST TYPE’,
url: ‘/request-path’,
payload: {
Put in the JSON object you’d like to pass through like:
name : ‘objectName’,
value : ‘actualValue’,
etc.
}
headers: {‘my-header’: ‘header-value’}
};
Of course, feel free to pick and choose which parts of injectOptions
your HTTP request actually needs!
To inject query parameters, you’ll need to include them directly in injectOption
’s request-path. Server.inject() will not auto-fill query parameters into a route. For example, if you have a route with path:
path: ‘/delete-object/{id?}’
Hitting that route with injectOptions
will looks something like:
const injectOptions = {
method: ‘DELETE’,
url: ‘/delete-object/86’
};
server.inject()
’s Return Valueserver.inject()
returns a Hapi ResponseObject
. To access the payload and test specific values, you can do something like:
const response = await testServer.inject(injectOptions);
const payload = JSON.parse((response.payload));
expect(payload.name).toBe(‘ExpectedName’);
In this article, you have learned how to artificially inject custom Hapi requests into routed methods. You should now be able to make requests with any method, header, url, and payload combo that you need. Hapi testing!
const testServer = new Hapi.Server();
const testController = new TestController();
describe(‘put tests’, () => {
//a beforeAll is a great place to declare the routes you’ll need for any tests within a describe block!
beforeAll( () => {
testServer.route( {
method: ‘PUT’,
path: `/${testObjectType}/{id}`
options: {
handler: testController.put()
}
});
});
it(‘should update testObject if the id exists’, () => {
const injectOptions = {
method: ‘PUT’,
path: ‘/myObject/80’,
payload: {
name: ‘newName’
}
}
const response = await testServer.inject(injectOptions)l
const payload = JSON.parse((response.payload));
expect(payload.name).toBe(‘newName’);
});
});