npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@nodeboot/test

v2.14.0

Published

Test framework for Node_Boot projects

Readme

NodeBoot Integration Test Framework

A comprehensive, extensible testing framework for NodeBoot applications that provides a plugin-based architecture for dependency injection testing, service mocking, and lifecycle management.

Table of Contents

  1. Architecture Overview
  2. Quick Start
  3. Hook Types: Setup vs Return Hooks
  4. Available Hooks
  5. Advanced Usage
  6. Best Practices
  7. Troubleshooting
  8. Migration Guide

Architecture Overview

The NodeBoot Test Framework follows a layered, plugin-based architecture designed for maximum extensibility and composability:

┌─────────────────────────────────────────────────────────┐
│                Test Runner Integration                   │
│            (Jest, Mocha, Vitest, etc.)                 │
├─────────────────────────────────────────────────────────┤
│              Custom Hook Libraries                      │
│       (JestHooksLibrary, MochaHooksLibrary, etc.)      │
├─────────────────────────────────────────────────────────┤
│                 Core Framework                          │
│    (NodeBootTestFramework, HookManager, HooksLibrary)  │
├─────────────────────────────────────────────────────────┤
│                   Hook System                           │
│        (Hook base class, lifecycle phases)             │
├─────────────────────────────────────────────────────────┤
│               NodeBoot Application                      │
│         (IoC Container, Services, Config)              │
└─────────────────────────────────────────────────────────┘

Key Design Principles

  1. Plugin Architecture: Everything is a hook that can be added, removed, or customized
  2. Lifecycle-Driven: Clear, predictable execution phases
  3. Priority-Based: Hooks execute in controlled order based on priority
  4. State Management: Hooks can store and share state across lifecycle phases
  5. Test Runner Agnostic: Core framework works with any test runner
  6. Composable: Hook libraries can extend and combine functionality

3. Hook System

The framework uses a priority-based hook system that executes in phases:

  • beforeStart: Setup before application starts
  • afterStart: Configuration after application starts
  • beforeTests: Setup before test suite runs
  • afterTests: Cleanup after test suite completes
  • beforeEachTest: Setup before each individual test
  • afterEachTest: Cleanup after each individual test

Quick Start

Installation

npm install @nodeboot/test @nodeboot/jest
# or
pnpm add @nodeboot/test @nodeboot/jest

Basic Usage

import {useNodeBoot} from "@nodeboot/jest";
import {MyApp} from "./MyApp";

describe("My App Integration Tests", () => {
    const {useHttp, useService, useConfig} = useNodeBoot(MyApp, ({useConfig, useMock, useEnv, usePactum}) => {
        // Test configuration
        useConfig({
            app: {port: 3001},
            database: {url: "sqlite::memory:"},
        });

        // Environment variables
        useEnv({NODE_ENV: "test"});

        // Enable Pactum for HTTP testing
        usePactum();

        // Mock a service
        useMock(EmailService, {
            sendEmail: jest.fn(() => Promise.resolve()),
        });
    });

    it("should handle API requests", async () => {
        const {get} = useHttp();

        const response = await get("/api/users");
        expect(response.status).toBe(200);
    });

    it("should access services", () => {
        const userService = useService(UserService);
        expect(userService).toBeDefined();
    });
});

Hook Types: Setup vs Return Hooks

The NodeBoot Test Framework provides two distinct types of hooks that serve different purposes in your test lifecycle:

Setup Hooks (Configuration Phase)

Setup hooks are called during test configuration and are used to prepare your test environment before the application starts. These hooks configure how your application will run during tests.

Key Characteristics:

  • Execute during the setup callback function passed to useNodeBoot()
  • Run before the application starts
  • Used for configuration, mocking, and environment setup
  • Cannot access running application services or HTTP endpoints
  • Changes take effect when the application starts

Usage Pattern:

const hooks = useNodeBoot(MyApp, ({useConfig, useMock, useEnv}) => {
    // These are Setup Hooks - they configure the test environment
    useConfig({database: {url: "sqlite::memory:"}});
    useMock(EmailService, {sendEmail: jest.fn()});
    useEnv({NODE_ENV: "test"});
});

Common Setup Hooks:

  • useConfig() - Override application configuration
  • useMock() - Mock service implementations
  • useEnv() - Set environment variables
  • usePactum() - Enable HTTP testing tools
  • useCleanup() - Register cleanup functions
  • useAddress() - Get server address after startup

Return Hooks (Runtime Phase)

Return hooks are returned from useNodeBoot() and are used during test execution to interact with your running application. These hooks provide access to services, repositories, and HTTP clients.

Key Characteristics:

  • Available after useNodeBoot() returns
  • Execute during test runtime when called
  • Used for interacting with the running application
  • Can access services, make HTTP requests, and query data
  • Provide the actual testing capabilities

Usage Pattern:

const {useService, useHttp, useRepository} = useNodeBoot(MyApp, setupCallback);

it("should work with services", () => {
    // These are Return Hooks - they interact with the running app
    const userService = useService(UserService);
    const {get, post} = useHttp();
    const userRepo = useRepository(UserRepository);
});

Common Return Hooks:

  • useService() - Access IoC container services
  • useRepository() - Access data repositories
  • useHttp() - HTTP client for API testing
  • useSupertest() - Supertest instance for HTTP testing
  • useConfig() - Access current configuration (read-only)
  • useSpy() - Create Jest spies on services

Execution Timeline

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   Setup Phase   │    │  Application     │    │  Test Execution │
│                 │    │  Startup         │    │                 │
│  Setup Hooks    │───▶│                  │───▶│  Return Hooks   │
│  - useConfig()  │    │  - Load config   │    │  - useService() │
│  - useMock()    │    │  - Start server  │    │  - useHttp()    │
│  - useEnv()     │    │  - Initialize    │    │  - useRepo()    │
└─────────────────┘    └──────────────────┘    └─────────────────┘

Best Practices

  1. Use Setup Hooks for Configuration:

    // ✅ Good - Configure before app starts
    useNodeBoot(App, ({useConfig, useMock}) => {
        useConfig({port: 3001});
        useMock(EmailService, mockImpl);
    });
  2. Use Return Hooks for Testing:

    // ✅ Good - Test the running application
    const {useService, useHttp} = useNodeBoot(App, setup);
    
    it("should work", () => {
        const service = useService(MyService);
        expect(service.doSomething()).toBeTruthy();
    });
  3. Don't Mix Hook Types:

    // ❌ Bad - Can't use return hooks in setup
    useNodeBoot(App, ({useConfig, useService}) => {
        // useService not available here
        useConfig({port: 3001});
        const service = useService(MyService); // This will fail!
    });
  4. Understand Timing:

    // ✅ Good - Right timing for each hook type
    const hooks = useNodeBoot(App, ({useConfig}) => {
        useConfig({database: {url: "test.db"}}); // Setup: before app starts
    });
    
    it("should access database", () => {
        const repo = hooks.useRepository(UserRepo); // Runtime: after app started
    });

Available Hooks

Setup Hooks (Configuration Phase)

These hooks are called during the setup callback passed to useNodeBoot() and configure your test environment before the application starts.

useConfig(config: object)

Override application configuration for tests.

useConfig({
    app: {port: 3001},
    database: {url: "test.db"},
    redis: {host: "localhost", port: 6380},
});

useEnv(variables: Record<string, string>)

Set environment variables for the test session.

useEnv({
    NODE_ENV: "test",
    API_KEY: "test-key",
    DEBUG: "true",
});

useMock(ServiceClass, mockImplementation)

Mock service methods with automatic cleanup.

// Mock with Jest functions
useMock(EmailService, {
    sendEmail: jest.fn(() => Promise.resolve()),
    validateEmail: jest.fn(() => true),
});

// Mock with plain implementations
useMock(PaymentService, {
    processPayment: () => ({success: true, transactionId: "test-123"}),
});

useAddress(callback: (address: string) => void)

Access the server's listening address after startup.

useAddress(address => {
    console.log("Server running at:", address);
    // Set up external test dependencies
});

useAppContext(callback: (context: ApplicationContext) => void)

Access the application context for advanced setup.

useAppContext(context => {
    expect(context.config).toBeDefined();
    expect(context.logger).toBeDefined();
    // Additional context validation or setup
});

usePactum(baseUrl?: string)

Enable Pactum.js integration for HTTP testing.

usePactum(); // Uses default server address
// or
usePactum("http://localhost:3001"); // Custom base URL

// Now you can use spec() from pactum directly in tests

useCleanup(hooks: { afterAll?: () => void, afterEach?: () => void })

Register cleanup functions that will be called automatically.

useCleanup({
    afterAll: () => {
        // Clean up test data, close connections, etc.
    },
    afterEach: () => {
        // Reset state between tests
    },
});

Runtime Hooks (Test Execution Phase)

useService(ServiceClass)

Get service instances from the IoC container.

const userService = useService(UserService);
const result = userService.findUser("123");

useRepository(RepositoryClass)

Get repository instances for data layer testing.

const userRepo = useRepository(UserRepository);
await userRepo.create({name: "Test User"});
const users = await userRepo.findAll();

useHttp(baseURL?: string)

Get HTTP client for API testing.

const {get, post, put, delete: del} = useHttp();

// GET request
const users = await get("/api/users");

// POST request with data
const newUser = await post("/api/users", {
    name: "John Doe",
    email: "[email protected]",
});

// With headers
const response = await get("/api/protected", {
    headers: {Authorization: "Bearer token"},
});

// Custom base URL
const externalApi = useHttp("https://api.external.com");
const data = await externalApi.get("/data");

useSupertest()

Get Supertest instance for HTTP testing with built-in assertions.

const request = useSupertest();

await request
    .get("/api/users")
    .expect(200)
    .expect("Content-Type", /json/)
    .expect(res => {
        expect(res.body).toHaveLength(1);
    });

useConfig()

Access the current configuration (read-only during test execution).

const config = useConfig();
const port = config.getNumber("app.port");
const dbUrl = config.getString("database.url");
const isProduction = config.getBoolean("app.production", false);

Dual-Purpose Hooks

Some hooks can be used both during setup and test execution phases.

useAppContext() (Setup & Return)

Can be used as both a setup hook (with callback) and return hook (direct access).

// Setup usage - configure during setup phase
useNodeBoot(App, ({useAppContext}) => {
    useAppContext(context => {
        // Configure based on application context
        console.log("App started with config:", context.config);
    });
});

// Return usage - access during test execution
const {useAppContext} = useNodeBoot(App, setup);
it("should access app context", () => {
    useAppContext(context => {
        expect(context.logger).toBeDefined();
        expect(context.config).toBeDefined();
    });
});

Advanced Usage

Creating Custom Hooks

import {Hook} from "@nodeboot/test";
import {NodeBootAppView} from "@nodeboot/core";

export class CustomDatabaseHook extends Hook {
    constructor() {
        super(1); // Priority (lower numbers run first)
    }

    override async beforeStart() {
        // Set up test database
        await this.setupTestDatabase();
    }

    override async afterTests() {
        // Clean up test database
        await this.cleanupTestDatabase();
    }

    override async beforeEachTest() {
        // Reset database state
        await this.resetDatabase();
    }

    // Setup hook touse the database connection
    use() {
        return {
            getConnection: () => this.getState("connection"),
            seedData: (data: any) => this.seedTestData(data),
        };
    }

    // Setup hook to configure database connection
    call(config: DatabaseConfig) {
        this.setState("config", config);
    }

    private async setupTestDatabase() {
        // Implementation
    }

    private async cleanupTestDatabase() {
        // Implementation
    }

    private async resetDatabase() {
        // Implementation
    }
}

Custom Hook Libraries

Create custom hook libraries for specific testing needs:

import {HooksLibrary} from "@nodeboot/test";
// Import Jest-specific Hooks library if using Jest
import {JestHooksLibrary} from "@nodeboot/jest";
import {CustomHook} from "./CustomHook";

export class MyCustomHooksLibrary extends JestHooksLibrary {
    customHook = new CustomDatabaseHook();

    override registerHooks(hookManager: HookManager) {
        super.registerHooks(hookManager);
        hookManager.addHook(this.customHook);
    }

    override getSetupHooks(): JestSetUpHooks {
        // Make sure to include base setup hooks
        const baseHooks = super.getSetupHooks();
        return {
            ...baseHooks,
            useCustom: this.customHook.call.bind(this.customHook),
        };
    }

    override getReturnHooks() {
        // Make sure to include base return hooks
        const baseHooks = super.getReturnHooks();
        return {
            ...baseHooks,
            useCustom: this.customHook.use.bind(this.customHook),
        };
    }
}

// Use custom library
const hooks = useNodeBoot(MyApp, setup, new MyCustomHooksLibrary());

Multi-Server Testing

Test applications with multiple servers or services:

describe("Multi-Service Integration", () => {
    const api = useNodeBoot(ApiApp, ({useConfig}) => {
        useConfig({app: {port: 3001}});
    });

    const auth = useNodeBoot(AuthApp, ({useConfig}) => {
        useConfig({app: {port: 3002}});
    });

    it("should communicate between services", async () => {
        const apiHttp = api.useHttp();
        const authHttp = auth.useHttp();

        // Test inter-service communication
        const token = await authHttp.post("/login", credentials);
        const data = await apiHttp.get("/protected", {
            headers: {Authorization: `Bearer ${token.data.token}`},
        });

        expect(data.status).toBe(200);
    });
});

Database Testing with Persistence

import {useNodeBoot} from "@nodeboot/jest";
import {spec} from "pactum";

describe("User API with Persistence", () => {
    const {useHttp, useRepository, useMock} = useNodeBoot(AppWithDatabase, ({useConfig, usePactum}) => {
        useConfig({
            database: {url: "sqlite::memory:"},
        });
        usePactum();
    });

    beforeEach(async () => {
        // Seed test data
        const userRepo = useRepository(UserRepository);
        await userRepo.create({
            id: "1",
            name: "Test User",
            email: "[email protected]",
        });
    });

    it("should fetch users from database", async () => {
        const response = await spec().get("/api/users").expectStatus(200).returns("res.body");

        expect(response).toHaveLength(1);
        expect(response[0].name).toBe("Test User");
    });

    it("should create new users", async () => {
        const newUser = {
            name: "John Doe",
            email: "[email protected]",
        };

        const response = await spec().post("/api/users").withJson(newUser).expectStatus(201).returns("res.body");

        expect(response.id).toBeDefined();
        expect(response.name).toBe(newUser.name);
    });
});

Best Practices

1. Test Organization

describe("User Management", () => {
    const hooks = useNodeBoot(App, commonSetup);

    describe("Authentication", () => {
        // Auth-specific tests
    });

    describe("Profile Management", () => {
        // Profile-specific tests
    });
});

2. Mock Strategy

// Mock external dependencies, keep internal services real
useMock(EmailService, {sendEmail: jest.fn()}); // External
useMock(PaymentGateway, {charge: jest.fn()}); // External
// Don't mock UserService, OrderService, etc. (internal business logic)

3. Configuration Management

// Use environment-specific configs
useNodeBoot(AppUnderTest, ({useConfig}) => {
    useConfig({
        app: {
            port: 20000,
        },
        database: {url: process.env.TEST_DB_URL || "sqlite::memory:"},
        redis: {host: "localhost", port: 6380}, // Test Redis instance
        external: {
            apiKey: "test-key",
            baseUrl: "http://localhost:8080", // Mock server
        },
    });
});

4. Data Management

const {useRepository} = useNodeBoot(AppUnderTest);

// Clean slate for each test
beforeEach(async () => {
    const db = useRepository(DatabaseRepository);
    await db.truncateAll();
    await db.seedTestData();
});

5. Async Testing

const {useService} = useNodeBoot(AppUnderTest);

it("should handle async operations", async () => {
    const service = useService(AsyncService);

    // Use proper async/await
    const result = await service.processAsync("data");
    expect(result).toBeDefined();

    // Verify async side effects
    const spy = useSpy(EmailService, "sendEmail");
    expect(spy).toHaveBeenCalled();
});

Troubleshooting

Common Issues

  1. Service Not Found Error

    Error: The class MyService is not decorated with @Service

    Ensure your service classes are properly decorated with @Service().

  2. IoC Container Not Found

    Error: IOC Container is required for useService hook to work

    Make sure your app is properly initialized with dependency injection.

  3. Port Conflicts Use random ports in tests:

    useConfig({app: {port: 0}}); // Random available port
  4. Memory Leaks in Tests Ensure proper cleanup:

    useCleanup({
        afterAll: () => {
            // Close connections, clear caches, etc.
        },
    });

Debugging Tips

  1. Enable Debug Logging

    useEnv({DEBUG: "nodeboot:*"});
  2. Inspect Hook Execution The framework logs hook execution order and timing.

  3. Verify Mock Calls

    const spy = useSpy(Service, "method");
    console.log("Mock calls:", spy.mock.calls);

Migration Guide

From Manual Setup

// Before (manual setup)
beforeAll(async () => {
    app = new MyApp();
    server = await app.start();
});

afterAll(async () => {
    await server.close();
});

// After (NodeBoot Test Framework)
const {useHttp} = useNodeBoot(MyApp);

Adding New Hooks

When adding new testing capabilities, create hooks that follow the framework patterns and integrate with the lifecycle system.

See Jest Custom Hooks for a concrete example.

License

MIT License - see LICENSE file for details.