@hapi/lab Documentation

repository·master·Indexed 20 days ago

https://github.com/hapijs/lab

A specialized, framework-agnostic test utility for Node.js. Part of the hapi ecosystem, @hapi/lab supports BDD and TDD patterns, TypeScript execution, and integrates with assertion libraries like @hapi/code. It features a CLI for test execution, coverage reporting (HTML, JSON, JUnit, etc.), and lifecycle hooks (before, after, beforeEach, afterEach) for managing test state and setup.

Tokens
12.3K
Snippets
35
Records
45
Agent score
71%

What's inside @hapi/lab

  1. Organize tests using experiments and lifecycle methods

    master

    Tests can be grouped into experiment() blocks. Within an experiment, you can define lifecycle hooks to run setup or teardown logic:

    • before(): Runs once before the experiment starts.
    • after(): Runs once after the experiment finishes.
    • beforeEach(): Runs before every individual test in the experiment.
    • afterEach(): Runs after every individual test in the experiment.

    All lifecycle methods support returning a Promise to handle asynchronous setup/teardown.

    const Code = require('@hapi/code');
    const Lab = require('@hapi/lab');
    const { expect } = Code;
    const lab = exports.lab = Lab.script();
    
    lab.experiment('math', () => {
    
        lab.before(() => {
            return new Promise((resolve) => {
                setTimeout(resolve, 1000);
            });
        });
    
        lab.beforeEach(() => {
            // Run before every single test
        });
    
        lab.test('returns true when 1 + 1 equals 2', () => {
            expect(1 + 1).to.equal(2);
        });
    });
  2. Manage shared state using context

    master

    The context object is passed to before, after, beforeEach, and afterEach hooks, as well as to the tests themselves. It is used to share properties between hooks and tests without relying on module-level variables.

    Key Behavior:

    • The context object is shallow cloned when passed to tests and child experiments.
    • Modifications made to the context inside a test or a nested experiment will not affect the context of other tests or the parent experiment.
    lab.experiment('my experiment', () => {
      lab.before(({ context }) => {
          context.foo = 'bar';
      })
    
      lab.test('contains context', ({ context }) => {
          expect(context.foo).to.equal('bar');
      });
    
      lab.experiment('a nested experiment', () => {
        lab.before(({ context }) => {
          context.foo = 'baz';
        });
    
        lab.test('has the correct context', ({ context }) => {
          expect(context.foo).to.equal('baz');
          context.foo = 'fizzbuzz'; // This change stays within this test/experiment
        });
    
        lab.test('receives a clean context', ({ context }) => {
          expect(context.foo).to.equal('baz');
        });
      });
    });
  3. Integrate an assertion library with Lab

    master

    You can integrate Lab with any assertion library using the --assert argument. When an assertion library is specified, it is imported and assigned to the Lab.assertions property.

    If you use @hapi/code, Lab provides additional features:

    • Missing Assertion Detection: Lab will report incomplete assertions (e.g., if you reference a property instead of calling a method) and return a failure.
    • Verbosity Reporting: Lab calculates the ratio of assertions to tests and outputs this value when using the console reporter.
    lab --assert @hapi/code
  4. Manage coverage with inline comments and a bypass stack

    master

    You can control which lines are included in coverage reports using special comments.

    Inline enabling/disabling

    Use $lab:coverage:off$ to exclude a block and $lab:coverage:on$ to resume coverage.

    Coverage bypass stack

    To handle complex scenarios like transpiled code (e.g., via Babel) where simple on/off toggles might overwrite each other, use a stack-based approach:

    • $lab:coverage:push$: Copies the current skip state to the top of the stack and keeps it as the current state.
    • $lab:coverage:pop$: Replaces the current skip state with the top of the stack and removes that top entry.

    Note: If you attempt to pop an empty stack, Lab will throw the error "unable to pop coverage bypass stack".

    // Example of using the stack for transpiled code
    /* $lab:coverage:off$ */
    const {
      types
    } =
    /*$lab:coverage:push$/
    /*$lab:coverage:off$*/
    _util
    /*$lab:coverage:pop$/
    .
    /*$lab:coverage:push$/
    /*$lab:coverage:off$*/
    default
    /*$lab:coverage:pop$/
    ;
    /* $lab:coverage:on$ */
  5. Run TypeScript tests with Lab

    master

    To run tests written in TypeScript, use the --typescript CLI option. Lab includes a TypeScript definition file to facilitate usage. If your project uses custom paths (e.g., via tsconfig-paths), you can pass --require 'tsconfig-paths/register' to the command.

    TypeScript Example:

    import * as Lab from '@hapi/lab';
    import { expect } from '@hapi/code';
    
    const lab = Lab.script();
    const { describe, it, before } = lab;
    export { lab };
    
    describe('experiment', () => {
        before(() => {});
        it('verifies 1 equals 1', () => {
            expect(1).to.equal(1);
        });
    });

    Execution Commands:

    # Basic TypeScript execution
    $ lab --typescript
    
    # TypeScript execution with custom path resolution
    $ lab --typescript --require 'tsconfig-paths/register'
  6. Debug Lab tests with V8 Inspector

    master

    To debug your tests using the V8 Inspector, use the --inspect flag. This will print a URL to the console that you can use with Chrome DevTools or other V8 inspector extensions.

    If your tests are mapped to npm test, you can pass the flag through npm: npm test -- --inspect.

    You can also specify a custom port: lab --inspect={port}.

    lab --inspect
  7. Install and run lab via npm

    master

    Add @hapi/lab as a development dependency in your package.json. You can define scripts to run tests with specific configurations, such as setting a coverage threshold or generating HTML coverage reports.

    By default, running lab loads all *.js, *.cjs, or *.mjs files inside the local test directory. To run specific files, pass them as arguments to the CLI.

    {
      "devDependencies": {
        "@hapi/lab": "21.x.x"
      },
      "scripts": {
        "test": "lab -t 100",
        "test-cov-html": "lab -r html -o coverage.html"
      }
    }
    # Run tests for a specific file
    $ lab unit.js
    
    # Run tests via npm
    $ npm test
  8. Implement BDD and TDD patterns in Lab

    master

    You can configure your Lab.script() export to follow Behavior Driven Development (BDD) or Test Driven Development (TDD) naming conventions by destructuring specific methods.

    // BDD Style
    const { describe, it, before, after } = exports.lab = Lab.script();
    
    describe('math', () => {
        it('returns true when 1 + 1 equals 2', () => {
            expect(1 + 1).to.equal(2);
        });
    });
    
    // TDD Style
    const { suite, test } = exports.lab = Lab.script();
    
    suite('math', () => {
        test('returns true when 1 + 1 equals 2', () => {
            expect(1 + 1).to.equal(2);
        });
    });
  9. Create a test script with Lab.script()

    master

    To use lab, you must require the module and export a test script using Lab.script(). The exported object must be named lab for the runner to find it. You can use either the it pattern or the test pattern.

    Note: lab works best with @hapi/code, but is compatible with any assertion library that throws an error on failure. For asynchronous tests, use async/await or return a Promise.

    const Code = require('@hapi/code');
    const Lab = require('@hapi/lab');
    
    const { expect } = Code;
    // Exporting the script as 'lab' is required
    const { it } = exports.lab = Lab.script();
    
    it('returns true when 1 + 1 equals 2', () => {
        expect(1 + 1).to.equal(2);
    });
  10. Use code coverage with ES Modules (ESM)

    master

    Lab does not natively support code coverage for ES modules. For ESM projects, it is recommended to use c8.

    Setup with c8:

    1. Install c8: npm install --save-dev c8.
    2. Update your test script in package.json to prefix the lab command with c8.
       "scripts": {
    -      "test": "lab -a @hapi/code -t 100"
    +      "test": "c8 --100 lab -a @hapi/code"
       }
  11. Configure ESLint with @hapi/eslint-plugin

    master

    To use the recommended hapi-specific linting rules with lab, you must add @hapi/eslint-plugin to your project and extend its configuration in your ESLint setup.

    1. Add @hapi/eslint-plugin as a dependency in package.json.
    2. In your ESLint configuration file, add "extends": "plugin:@hapi/recommended".

    To ignore specific files in your ESLint configuration while preserving hapi rules, use the ignores property.

    import HapiPlugin from '@hapi/eslint-plugin';
    
    export default [
        {
            ignores: ['node_modules/*', '**/vendor/*.js'],
        },
        ...HapiPlugin.configs.module,
    ];