Testing

This tutorial is compatible with hapi v17

Overview

Hapi is designed for creating robust, testable applications. To this end, Hapi includes the ability to test routes without having to actually start a server, completely avoiding the time overheads and added complexity of the TCP protocol.

This tutorial goes into a basic setup for testing routes, and outlines one possible setup for a testable application using lab and code.

lab

lab is a simple test utility for Node.js. Unlike other test utilities, lab uses only async/await features and includes everything you should expect from a modern Node.js test utility. lab works with any assertion library that throws an error when a condition isn't met. For this tutorial, you will be using the code assertion library.

To install lab, type the following in your terminal:

npm install —save-dev @hapi/lab

code

code is based on the chai assertions library. It was created to be a small, simple, and intuitive assertions library that could be run without plugins, extensions, and have low overhead.

To install code, type the following in your terminal:

npm install —save-dev @hapi/code

Server Setup

Taking the server example from the Getting Started tutorial, we make a minor modification to it, such that it doesn't automatically start when referenced from our tests. You might call this file server.js and place it in the lib directory of your project:

  1. 'use strict';
  2.  
  3. const Hapi = require('@hapi/hapi');
  4.  
  5. const server = Hapi.server({
  6. port: 3000,
  7. host: 'localhost'
  8. });
  9.  
  10. server.route({
  11. method: 'GET',
  12. path: '/',
  13. handler: function () {
  14.  
  15. return 'Hello World!';
  16. }
  17. });
  18.  
  19. exports.init = async () => {
  20.  
  21. await server.initialize();
  22. return server;
  23. };
  24.  
  25. exports.start = async () => {
  26.  
  27. await server.start();
  28. console.log(`Server running at: ${server.info.uri}`);
  29. return server;
  30. };
  31.  
  32. process.on('unhandledRejection', (err) => {
  33.  
  34. console.log(err);
  35. process.exit(1);
  36. });

You now export, but do not call, init() and start(). This will allow you to initialize and start the server from different files. The init() function will initialize the server (starts the caches, finalizes plugin registration) but does not start the server. This is what you will use in your tests. The start() function will actually start the server. This is what you will use in our main entry-point for the server:

  1. `use strict`;
  2.  
  3. const { start } = require('lib/server');
  4.  
  5. start();

What you've created here is a way of starting the server normally by calling its start function in our entry-point, and exposing a port for external HTTP traffic, but you've also got a module which doesn't do anything by default, which you can use in our tests.

Writing a Route Test

In this example you'll use lab, but the same method can be used for any testing tool such as Mocha, Jest, Tap, Ava etc.

By default, lab loads all the '*.js' files inside the local test directory and executes the tests found. To use different directories or files, pass the file or directories as arguments:

$ lab unit.js

To get started, create a file called example.test.js in the test directory:

  1. 'use strict';
  2.  
  3. const Lab = require('@hapi/lab');
  4. const { expect } = require('@hapi/code');
  5. const { afterEach, beforeEach, describe, it } = exports.lab = Lab.script();
  6. const { init } = require('../lib/server');
  7.  
  8. describe('GET /', () => {
  9. let server;
  10.  
  11. beforeEach(async () => {
  12. server = await init();
  13. });
  14.  
  15. afterEach(async () => {
  16. await server.stop();
  17. });
  18.  
  19. it('responds with 200', async () => {
  20. const res = await server.inject({
  21. method: 'get',
  22. url: '/'
  23. });
  24. expect(res.statusCode).to.equal(200);
  25. });
  26. });

Here you are testing whether or not our 'GET' route will respond with a 200 status code. You first call describe() to provide the structure of your test. describe() takes two parameters, a description of the test, and the function that will setup the test.

Note that you call init rather than start to set up the server, which means that the server starts, but does not listen on a socket. After each test you call stop to cleanup and stop the server.

The it() function is what will run your test. it() takes two parameters, a description of a successful test, and a function to run the test.

You will note the use of inject on the server. inject uses Shot to inject a request directly into hapi's route handler. This is the magic which allows us to test HTTP methods.

To run the tests, you can modify the package.json of your project to run your test runner:

  1. "scripts": {
  2. "test": "lab -v **/*.spec.js"
  3. }