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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pmouli/hap-test

v0.4.0

Published

Test harness for Homebridge plugin development - write integration tests without physical HomeKit controllers

Readme

hap-test

Comprehensive test harness for Homebridge plugin development

npm version License: MIT TypeScript Tests

Overview

hap-test is a comprehensive testing framework for Homebridge plugins that enables developers to write integration and end-to-end tests without requiring physical HomeKit controllers or a running Homebridge instance. Test your plugins with confidence using mocked Homebridge APIs, simulated HomeKit interactions, and deterministic time control.

Why hap-test?

  • 🚀 Fast: No network access, no physical devices, tests complete in milliseconds
  • 🎯 Deterministic: Fake timers eliminate flaky tests from timing issues
  • ✅ Complete: Full Homebridge API mock with lifecycle events
  • 🛡️ Type-Safe: Full TypeScript support with comprehensive types
  • 📊 Validated: Automatic HAP protocol validation catches bugs early
  • 🧪 Flexible: Test success paths, error scenarios, and edge cases
  • 🔍 Debuggable: Debug mode with detailed logging of all operations

Features

Core Testing Infrastructure

  • TestHarness: Orchestrates complete Homebridge environment for testing
  • MockHomebridgeAPI: Complete mock of Homebridge platform API
  • MockHomeKit: Simulates Apple Home controller interactions
  • TimeController: Deterministic time-based testing with Vitest integration

Advanced Capabilities

  • HAP Protocol Validation: Automatic validation of characteristic constraints (min/max/step/format)
  • Event System: Subscribe to and test characteristic value change events
  • Network Simulation: Test resilience with simulated latency, packet loss, and disconnection
  • Cached Accessories: Test accessory restoration across Homebridge restarts
  • Multi-User Support: Test scenarios with multiple HomeKit controllers
  • Custom Matchers: Vitest matchers for common HomeKit assertions

Installation

npm install --save-dev hap-test
# or
pnpm add -D hap-test
# or
yarn add -D hap-test

Peer Dependencies

npm install --save-dev homebridge hap-nodejs hap-fluent vitest

Required Versions:

  • homebridge >= 1.11.0
  • hap-nodejs >= 0.13.0
  • hap-fluent >= 0.3.0
  • vitest >= 2.0.0

Quick Start

Here's a complete test for a lightbulb platform:

import { describe, test, expect, afterEach } from 'vitest';
import { TestHarness } from 'hap-test';
import { LightbulbPlatform } from '../src/platform';

describe('LightbulbPlatform', () => {
  let harness: TestHarness;

  afterEach(async () => {
    if (harness) {
      await harness.shutdown();
    }
  });

  test('should register lightbulb accessory', async () => {
    // Create test harness with your platform
    harness = await TestHarness.create({
      platformConstructor: LightbulbPlatform,
      platformConfig: {
        platform: 'LightbulbPlatform',
        name: 'Test Lightbulb',
        devices: [{ id: 'light-1', name: 'Living Room' }],
      },
    });

    // Wait for platform to register accessories
    await harness.waitForAccessories(1);

    // Get registered accessory
    const accessories = harness.homekit.accessories();
    expect(accessories).toHaveLength(1);
    expect(accessories[0].displayName).toBe('Living Room');

    // Get lightbulb service
    const service = harness.homekit.service(accessories[0].UUID, 'Lightbulb');
    expect(service).toBeDefined();

    // Test characteristic operations
    const onChar = service?.getCharacteristic('On');
    expect(onChar?.value).toBe(false);

    // Simulate HomeKit setting value
    await service?.setCharacteristic('On', true);
    expect(onChar?.value).toBe(true);
  });
});

Core Concepts

TestHarness

The central orchestrator that manages the complete test environment:

const harness = await TestHarness.create({
  // Your platform class
  platformConstructor: MyPlatform,

  // Platform configuration
  platformConfig: {
    platform: 'MyPlatform',
    name: 'Test Platform',
    // ... your config
  },

  // Optional: cached accessories from previous session
  cachedAccessories: [],

  // Optional: enable debug logging
  debug: true,

  // Optional: custom timeouts
  timeouts: {
    startup: 5000,
    registration: 3000,
  },
});

// Access components
harness.homekit;  // MockHomeKit controller
harness.api;      // MockHomebridgeAPI
harness.time;     // TimeController
harness.network;  // NetworkSimulator

// Cleanup
await harness.shutdown();

MockHomeKit

Simulates an Apple Home controller:

// Get all registered accessories
const accessories = harness.homekit.accessories();

// Get specific accessory
const accessory = harness.homekit.accessory('accessory-uuid');

// Get service on accessory
const service = harness.homekit.service('accessory-uuid', 'Lightbulb');

// Get characteristic
const char = harness.homekit.characteristic('accessory-uuid', 'Lightbulb', 'On');

// Simulate HomeKit operations
await service.setCharacteristic('On', true);
const value = await service.getCharacteristic('On');

// Subscribe to events
const subscription = char.subscribe();
const event = await subscription.waitForNext(1000);

TimeController

Control time for deterministic testing:

// Advance time by 30 seconds
await harness.time.advance(30000);

// Freeze time at specific point
harness.time.freeze();

// Set specific time
harness.time.setTime(new Date('2025-01-01T00:00:00Z'));

// Get current fake time
const now = harness.time.now();

// Reset to real time
harness.time.reset();

NetworkSimulator

Simulate network conditions:

// Enable network simulation
const harness = await TestHarness.create({
  platformConfig: myConfig,
  networkSimulation: { enabled: true },
});

// Add latency (affects all operations)
harness.network.setLatency(200); // 200ms delay

// Simulate packet loss
harness.network.setPacketLoss(0.1); // 10% packet loss

// Disconnect network
harness.network.disconnect();

// Reconnect
harness.network.reconnect();

// Reset to normal
harness.network.reset();

Advanced Features

Event Subscriptions

Test asynchronous platform behavior:

const char = harness.homekit.characteristic(uuid, 'Lightbulb', 'On');

// Subscribe to value changes
const subscription = char.subscribe();

// Platform changes value
await platform.updateCharacteristic();

// Wait for event
const event = await subscription.waitForNext(5000);
expect(event.newValue).toBe(true);

// Check event history
const history = subscription.getHistory();
console.log('Events:', history);

// Cleanup
subscription.unsubscribe();

HAP Protocol Validation

Automatic validation of HAP constraints:

const service = harness.homekit.service(uuid, 'Lightbulb');

// ✅ Valid operation
await service.setCharacteristic('Brightness', 75);

// ❌ Throws CharacteristicValidationError
await expect(
  service.setCharacteristic('Brightness', 150) // Max is 100
).rejects.toThrow('exceeds maximum 100');

// ❌ Throws for wrong format
await expect(
  service.setCharacteristic('Brightness', 'bright') // Must be number
).rejects.toThrow('Expected number');

// ❌ Throws for invalid enum
await expect(
  service.setCharacteristic('TargetHeatingCoolingState', 99)
).rejects.toThrow('not in valid values');

Cached Accessories

Test accessory restoration across restarts:

// First session - register accessories
let harness = await TestHarness.create({
  platformConstructor: MyPlatform,
  platformConfig: myConfig,
});

await harness.waitForAccessories(2);
const cached = harness.api.getRegisteredAccessories();
await harness.shutdown();

// Second session - restore from cache
harness = await TestHarness.create({
  platformConstructor: MyPlatform,
  platformConfig: myConfig,
  cachedAccessories: cached, // Restored!
});

// Platform's configureAccessory() was called
expect(harness.homekit.accessories()).toHaveLength(2);

Time-Based Testing

Test polling, schedules, and timeouts:

test('polls device every 30 seconds', async () => {
  const harness = await TestHarness.create({
    platformConstructor: PollingPlatform,
    platformConfig: myConfig,
  });

  const char = harness.homekit.characteristic(uuid, 'Lightbulb', 'On');

  // Initial state
  expect(char.value).toBe(false);

  // Advance 30 seconds
  await harness.time.advance(30000);

  // Platform should have polled
  expect(char.value).toBe(true);

  // Advance another 30 seconds
  await harness.time.advance(30000);

  // Another poll occurred
  expect(char.value).toBe(false);
});

Error Scenarios

Test error handling and recovery:

test('handles device errors gracefully', async () => {
  const harness = await TestHarness.create({
    platformConstructor: MyPlatform,
    platformConfig: myConfig,
    networkSimulation: { enabled: true },
  });

  const service = harness.homekit.service(uuid, 'Lightbulb');

  // Simulate network failure
  harness.network.disconnect();

  // Operation should fail
  await expect(
    service.setCharacteristic('On', true)
  ).rejects.toThrow('Network disconnected');

  // Reconnect
  harness.network.reconnect();

  // Should work now
  await service.setCharacteristic('On', true);
  expect(service.getCharacteristic('On')?.value).toBe(true);
});

Custom Matchers

Vitest matchers for common assertions:

import 'hap-test/matchers';

test('using custom matchers', async () => {
  const harness = await TestHarness.create({...});
  const accessory = harness.homekit.accessory(uuid);
  const service = harness.homekit.service(uuid, 'Lightbulb');
  const char = harness.homekit.characteristic(uuid, 'Lightbulb', 'Brightness');

  // Accessory matchers
  expect(accessory).toBeRegistered();
  expect(accessory).toHaveService('Lightbulb');

  // Service matchers
  expect(service).toHaveCharacteristic('On');
  expect(service).toHaveCharacteristic('Brightness');

  // Characteristic matchers
  expect(char).toHaveValue(75);
  expect(char).toBeInRange(0, 100);
  expect(char).toHaveFormat('int');
});

Examples

Check the /examples directory for complete working examples:

  • basic-accessory-test.ts - Simple lightbulb platform test
  • debug-mode.ts - Using debug logging
  • error-scenarios.ts - Testing error handling
  • hap-protocol-validation.ts - HAP validation examples
  • multi-device-platform.ts - Multiple accessories
  • time-based-features.ts - Polling and schedules

Documentation

Testing Best Practices

1. Always Clean Up

let harness: TestHarness;

afterEach(async () => {
  if (harness) {
    await harness.shutdown();
  }
});

2. Wait for Registration

const harness = await TestHarness.create({...});
await harness.waitForAccessories(expectedCount, timeout);

3. Use Time Control

// ❌ Flaky - uses real time
await new Promise(resolve => setTimeout(resolve, 1000));

// ✅ Deterministic - uses fake time
await harness.time.advance(1000);

4. Enable Debug Mode

const harness = await TestHarness.create({
  platformConfig: myConfig,
  debug: true, // See all operations
});

5. Test Error Cases

// Test both success and failure
test('handles invalid values', async () => {
  await expect(
    service.setCharacteristic('Brightness', -1)
  ).rejects.toThrow();
});

TypeScript Configuration

Add to your tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "types": ["vitest/globals", "node"]
  },
  "include": ["test/**/*.ts"]
}

Vitest Configuration

Add to your vitest.config.ts:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    coverage: {
      enabled: true,
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      include: ['src/**/*.ts'],
    },
  },
});

Performance

hap-test is designed for speed:

  • Harness initialization: < 50ms
  • Accessory registration: < 10ms per accessory
  • Characteristic operations: < 1ms
  • Full test suite: Typically < 1 second

Compatibility

  • Homebridge: 1.11.0+
  • HAP-NodeJS: 0.13.0+
  • Node.js: 18+
  • TypeScript: 5.0+
  • Vitest: 2.0+

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for details.

License

MIT © Pradeep Mouli

Support

Acknowledgments

  • Built for Homebridge plugin development
  • Works seamlessly with hap-fluent for type-safe characteristic access
  • Powered by Vitest for fast, modern testing

Status: Production Ready | Version: 0.1.0 | Tests: 134 passing

  format: 'bool',
  perms: ['pr', 'pw', 'ev'],
});

service.addCharacteristic(onChar);
accessory.addService(service);
harness.homeKit.addAccessory(accessory);

// Test characteristic operations
await onChar.setValue(true);
expect(await onChar.getValue()).toBe(true);

// Test event subscriptions
const subscription = onChar.subscribe();
await onChar.setValue(false);

const history = subscription.getHistory();
expect(history.length).toBeGreaterThan(0);
expect(history[history.length - 1].newValue).toBe(false);

subscription.unsubscribe();
harness.shutdown();

}); });


See [examples/basic-accessory-test.ts](./examples/basic-accessory-test.ts) for more detailed usage.

## Documentation

- [Getting Started](./docs/getting-started.md)
- [Advanced Testing](./docs/advanced-testing.md)
- [Migration Guide](./docs/migration-guide.md)
- [API Reference (summary)](./docs/api.md) — generate HTML via `pnpm --filter hap-test docs:api`
- [Examples](./examples/)

## Requirements

- Node.js >= 18.0.0
- Homebridge >= 1.6.0 or 2.x
- HAP-NodeJS >= 0.11.0
- Vitest >= 1.0.0

## Development Status

🚧 **Phase 6 (Developer Experience) - In Progress**

Current capabilities:
- ✅ Core infrastructure (TestHarness, MockHomebridgeAPI, MockHomeKit, TimeController)
- ✅ HAP protocol validation and permissions enforcement
- ✅ Event subscriptions and history tracking
- ✅ Network simulation (latency, packet loss, disconnect)
- ✅ Custom Vitest matchers
- ✅ Example suites covering common devices

API may change before 1.0.0 release.

## License

MIT © HAP Fluent Monorepo Contributors