hapi — Inject Request Payload, Headers and Parameters While Testing Route Handlers

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

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!


Mentioned Resources

Explore the Library

Find interesting tutorials and solutions for your problems.