devinivy / labbable

No-fuss hapi server testing
MIT License
25 stars 5 forks source link

labbable

No-fuss hapi server testing

Build Status Coverage Status

Lead Maintainer - Devin Ivy

Note

Under hapi v17+ and async/await, labbable no longer provides much utility and is deprecated until any further needs arise surrounding hapi testing. It is suggested that your server entrypoint export a function that asynchronously returns a hapi server and starts/initializes the server as is appropriate. For a full-fledged example, see this server and this corresponding test.

Here is a simple example just for illustrative purposes.

server.js


const Hapi = require('hapi');

exports.deployment = async (start) => {

const server = Hapi.server();

await server.initialize();

if (!start) {
    return server;
}

await server.start();

console.log('Server started');

return server;

};

if (!module.parent) { exports.deployment(true).catch(console.error); }


> #### `test/index.js`
```js

// Load modules

const Code = require('code');
const Lab = require('lab');
const Server = require('../server');

// Test shortcuts

const { describe, it } = exports.lab = Lab.script();
const { expect } = Code;

describe('Deployment', () => {

    it('returns a server.', async () => {

        const server = await Server.deployment();

        expect(server.route).to.be.a.function();
    });
});

Introduction

It can be a pain to get your hapi server into your tests, especially when using otherwise wonderful tools such as glue. Labbable makes this process very simple, and encourages the best practice of testing an initialized (but not started) hapi server.

Why initialize the server for tests?

Plugin dependencies are only enforced at the time of server initialization. This means code that relies on a plugin being present (typically by the after callback of server.dependency(deps, after) will only run during initialization. And if there are any dependencies missing, those errors will surface only during initialization. Your server's caches will also be started and onPreStart server extensions will run.

Should you so desire, labbable can also pass an uninitialized server into your tests using options for labbable.ready().

Usage

See also the API Reference

Directly (as plugin)

In this case the server is immediately available and can be placed in module.exports. Registering the Labbable.plugin hapi plugin adds a server decoration server.labbableReady() that can be used in a test to guarantee the server is initialized.

server.js

const Hapi = require('hapi');
const Labbable = require('labbable');

// Step 1.
// Simply export your server
const server = module.exports = new Hapi.Server();

server.connection();

// Step 2.
// Register the labbable plugin plus any others
server.register([Labbable.plugin], (err) => {

    if (err) {
        throw err;
    }

    // Step 3.
    // Initialize your server
    server.initialize((err) => {

        if (err) {
            throw err;
        }

        // Don't continue to start server if module
        // is being require()'d (likely in a test)
        if (module.parent) {
            return;
        }

        server.start((err) => {

            if (err) {
                throw err;
            }

            console.log('Server started');
        });
    });
});

test/index.js

const Code = require('code');
const Lab = require('lab');
const MyServer = require('../server.js');

const lab = exports.lab = Lab.script();
const describe = lab.describe;
const before = lab.before;
const it = lab.it;
const expect = Code.expect;

describe('My server', () => {

    const server = MyServer;

    before((done) => {

        // Callback fires once the server is initialized
        // or immediately if the server is already initialized
        server.labbableReady((err) => {

            if (err) {
                return done(err);
            }

            return done();
        });
    });

    // server is now available to be tested
    it('initializes.', (done) => {

        // server.isInitialized() can be used to check the server's init state
        expect(server.isInitialized()).to.equal(true);
        done();
    });
});

With glue

In this case the server is composed by glue then made available asynchronously, so it can't be exported as in the previous example.

Instead we export an instance lababble of Labbable, then call labbable.using(server) as soon as the server is available. The method labbable.ready() can then be used in a test to get a hold of server once it's initialized.

server.js

const Glue = require('glue');
const Labbable = require('labbable');

// Step 1.
// Make an instance of Labbable
// to which we can pass the server
const labbable = module.exports = new Labbable();
const manifest = {/* ... */};

Glue.compose(manifest, (err, server) => {

    if (err) {
        throw err;
    }

    // Step 2.
    // Show the server to our instance of labbable
    labbable.using(server);

    // Step 3.
    // Initialize your server
    server.initialize((err) => {

        if (err) {
            throw err;
        }

        // Don't continue to start server if module
        // is being require()'d (likely in a test)
        if (module.parent) {
            return;
        }

        server.start((err) => {

            if (err) {
                throw err;
            }

            console.log('Server started');
        });
    });
});

test/index.js

const Code = require('code');
const Lab = require('lab');
const LabbableServer = require('../server.js');

const lab = exports.lab = Lab.script();
const describe = lab.describe;
const before = lab.before;
const it = lab.it;
const expect = Code.expect;

describe('My server', () => {

    let server;

    before((done) => {

        // Callback fires once the server is initialized
        // or immediately if the server is already initialized
        LabbableServer.ready((err, srv) => {

            if (err) {
                return done(err);
            }

            server = srv;

            return done();
        });
    });

    // server is now available to be tested
    it('initializes.', (done) => {

        expect(server).to.exist();

        // isInitialized() can be used to check the server's init state
        expect(LabbableServer.isInitialized()).to.equal(true);
        done();
    });
});