Testing in hapi builds on top of a single method: server.inject
. Injecting requests is a core element in testing your hapi app.
A previous tutorial walked you through the basics of injecting requests to test route handlers. What we didn’t cover there is the ability to inject requests with payload, headers, and parameters. Let’s tackle that now!
hapi Series Overview
- What You’ll Build
- Prepare Your Project: Stack & Structure
- Environment Variables and Storing Secrets
- Set Up MongoDB and Connect With Mongoose
- Sending Emails in Node.js
- Load the User’s Profile Picture From Gravatar Using Virtuals in Mongoose
- Implement a User Profile Editing Screen
- Generate a Username in Mongoose Middleware
- Displaying Seasons and Episodes for TV Shows with Mongoose Relationship Population
- Implementing Pagination for Movies
- Implement a Watchlist
- Create a Full Text Search with MongoDB
- Create a REST API with JSON Endpoints
- Update Mongoose Models for JSON Responses
- API Pagination for TV Shows
- Customize API Endpoints with Query Parameters
- Always Throw and Handle API Validation Errors
- Advanced API Validation With Custom Errors
- Create an API Documentation with Swagger
- Customize Your Swagger API Documentation URL
- Describe Endpoint Details in Your Swagger API Documentation
- 10 Tips on API Testing With Postman
- JWT Authentication in Swagger API Documentation
- API Versioning with Request Headers
- API Login With Username and Password to Generate a JWT
- JWT Authentication and Private API Endpoints
- Refresh Tokens With JWT Authentication
- Create a JWT Utility
- JWT Refresh Token for Multiple Devices
- Check Refresh Token in Authentication Strategy
- Rate Limit Your Refresh Token API Endpoint
- How to Revoke a JWT
- Invalidate JWTs With Blacklists
- JWT Logout (Part 1/2)
- JWT “Immediate” Logout (Part 2/2)
- A Better Place to Invalidate Tokens
- How to Switch the JWT Signing Algorithm
- Roll Your Own Refresh Token Authentication Scheme
- JWT Claims 101
- Use JWT With Asymmetric Signatures (RS256 & Co.)
- Encrypt the JWT Payload (The Simple Way)
- Increase JWT Security Beyond the Signature
- Unsigned JSON Web Tokens (Unsecured JWS)
- JWK and JWKS Overview
- Provide a JWKS API Endpoint
- Create a JWK from a Shared Secret
- JWT Verification via JWKS API Endpoint
- What is JOSE in JWT
- Encrypt a JWT (the JWE Way)
- Authenticate Encrypted JWTs (JWE)
- Encrypted and Signed JWT (Nested JWT)
- Bringing Back JWT Decoding and Authentication
- Bringing Back JWT Claims in the JWT Payload
- Basic Authentication With Username and Password
- Authentication and Remember Me Using Cookies
- How to Set a Default Authentication Strategy
- Define Multiple Authentication Strategies for a Route
- Restrict User Access With Scopes
- Show „Insufficient Scope“ View for Routes With Restricted Access
- Access Restriction With Dynamic and Advanced Scopes
- hapi - How to Fix „unknown authentication strategy“
- Authenticate with GitHub And Remember the Login
- Authenticate with GitLab And Remember the User
- How to Combine Bell With Another Authentication Strategy
- Custom OAuth Bell Strategy to Connect With any Server
- Redirect to Previous Page After Login
- How to Implement a Complete Sign Up Flow With Email and Password
- How to Implement a Complete Login Flow
- Implement a Password-Reset Flow
- Views in hapi 9 (and above)
- How to Render and Reply Views
- How to Reply and Render Pug Views (Using Pug 2.0)
- How to Create a Dynamic Handlebars Layout Template
- Create and Use Handlebars Partial Views
- Create and Use Custom Handlebars Helpers
- Specify a Different Handlebars Layout for a Specific View
- How to Create Jade-Like Layout Blocks in Handlebars
- Use Vue.js Mustache Tags in Handlebars Templates
- How to Use Multiple Handlebars Layouts
- How to Access and Handle Request Payload
- Access Request Headers
- How to Manage Cookies and HTTP States Across Requests
- Detect and Get the Client IP Address
- How to Upload Files
- Quick Access to Logged In User in Route Handlers
- How to Fix “handler method did not return a value, a promise, or throw an error”
- How to Fix “X must return an error, a takeover response, or a continue signal”
- Query Parameter Validation With Joi
- Path Parameter Validation With Joi
- Request Payload Validation With Joi
- Validate Query and Path Parameters, Payload and Headers All at Once on Your Routes
- Validate Request Headers With Joi
- Reply Custom View for Failed Validations
- Handle Failed Validations and Show Errors Details at Inputs
- How to Fix AssertionError, Cannot validate HEAD or GET requests
- Getting Started With Testing Using Lab and Code
- Run Tests with Assertions using Code
- Test Route Handlers by Injecting Requests
- Inject Request Payload, Headers and Parameters While Testing Route Handlers
Inject Request Payload
Web apps have different types of endpoints. You’ll serve static assets (images, CSS, JS), render web views, or reply data in another format, like JSON or XML. Your platform might also use forms or API requests sending data to the server.
Testing routes that expect data in the request payload is no problem for hapi. The options for server.inject
allow you to pass a payload
property with the injection.
// … imports (lab, code), assignments, export lab script, etc. :)
describe('requests with payload, headers and params,', () => {
it('injects payload', async () => {
const server = new Hapi.Server()
// add route to hapi server with handler that directly returns the request payload
server.route({
method: 'POST',
path: '/',
handler: request => {
// you can do assertions here, too
expect(request.payload).to.exist()
return request.payload
}
})
// remember: injecting payload isn’t available on GET requests
// use HTTP methods that support request payloads, like POST and PUT
const injectOptions = {
method: 'POST',
url: '/',
payload: {
name: 'Marcus',
isDeveloper: true,
isHapiPassionate: 'YEEEEAHHH'
}
}
const response = await server.inject(injectOptions)
// expect(…)
})
})
The sample code illustrates the request payload
property that uses a plain JavaScript object (consisting of three properties: name
, isDeveloper
, isHapiPassionate
). The route handler for this test expects the request payload to exist and returns it as a response. You can then do your assertions for the awaited response and the expected response payload.
This example calls expect()
in the sample route handler. You can do assertions there, too. This might be helpful to test plugins which do their magic before the route handler. An example where is this beneficial is a plugin that decorates the request object and want to test whether the plugin works correctly.
Notice that GET
requests don’t allow request payload. Hapi ignores incoming request payload in case you’re passing it along. The injected request will hit the specified route handler without the payload.
Inject Request Headers
Does your app behave differently when providing specific request headers? Our hapi-dev-errors plugin does. It renders a web view with error details about a failing operation by default. If you provide a Content-Type
request header with a value containing “json”, like Content-Type: application/json
, it responds the error details as JSON.
Test situations like this by using the headers
property in the injected request options.
// … imports (lab, code), assignments, export lab script, etc. :)
describe('requests with payload, headers and params,', () => {
it('injects headers', async () => {
const server = new Hapi.Server()
server.route({
method: 'GET',
path: '/',
handler: request => {
if (request.headers['x-custom-header']) {
return {
a: 'different object',
based: 'on the request headers'
}
}
}
})
const injectOptions = {
method: 'GET',
url: '/',
headers: { 'x-custom-header': 'my-funky-value' }
}
const response = await server.inject(injectOptions)
const payload = JSON.parse(response.payload || {})
expect(payload.a).to.exist()
expect(payload.based).to.exist()
})
})
The sample route implements a route handler that returns an object for a present x-custom-header
.
Inject Query and Path Parameters
Not every route in your project has a static route. Dynamic routes with path parameters become part of the project and you want to test them as well. Do that by injecting requests with a corresponding path.
The server.inject
method doesn’t provide an option for query parameters. You need to add them either directly to the injected request path or use a helper utility like Node.js’ qs
module and append them.
// … imports (lab, code), assignments, export lab script, etc. :)
describe('requests with payload, headers and params,', () => {
it('injects path params', async () => {
const server = new Hapi.Server()
server.route({
method: 'GET',
path: '/{framework}',
handler: request => {
return {
params: request.params
query: request.query
}
}
})
const response = await server.inject('/hapi?name=Marcus')
const payload = JSON.parse(response.payload || {})
// expect payload.params.framework = hapi
// expect payload.query.name = Marcus
})
})
qs
is helpful if you want to create a query parameter string from a JavaScript object. It takes care of using the ?
or &
characters to compose a string for query parameters.
This time we don’t use an object and pass it to server.inject
and instead provide the request URL directly. Passing a string value assumes a GET
request on the given path.
Inject Request All at Once
Of course you can combine all options and inject a more complex request. Let’s compose an example that sends request payload, headers and parameters to an endpoint:
'use strict'
// … imports (lab, code), assignments, export lab script, etc. :)
describe('requests with payload, headers and params,', () => {
it('injects payload, headers and params', async () => {
const server = new Hapi.Server()
// add route(s) here
// skipped to focus on the injected options object
const injectOptions = {
method: 'POST',
url: '/marcus?isDeveloper=yes',
payload: { team: 'Future Studio' },
headers: { 'x-custom-header': 'my-funky-value' }
}
const response = await server.inject(injectOptions)
// expect(…)
})
})
The inject options are a powerful mechanism to inject fine-grained requests that hit your route handlers. If your handlers behaves differently depending on values from headers, payload or parameters, test all these cases with ease!
Outlook
Injecting data with requests is a great way to test different types of applications. Test your web routes that render views. Do you expect a JSON response instead of HTML when providing a request header? No problem. Inject payload and parameters to your requests and test any situation.
You might notice that each individual test creates their own hapi server instance. You don’t want your tests to rely on each other. Creating a global hapi server would share the instance across tests.
In the next tutorial, we’ll optimize the hapi server initialization by running a function before each test and initialize a fresh hapi server there 👌
We’re happy to hear your thoughts and comments. Please don’t hesitate to use the comments below or find us on Twitter @futurestud_io. Let us know what you think!
Enjoy coding & make it rock!