# How to Test the BitChat BLE Transport Without Bluetooth Hardware

> Test the BitChat BLE transport without hardware. Learn how to use MockBLEService and simulated link-layer abstractions in the permissionlesstech/bitchat repo for efficient testing.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: how-to-guide
- Published: 2026-08-19

---

**Yes, the BitChat BLE transport can be fully tested without Bluetooth hardware using the in-memory `MockBLEService` and simulated link-layer abstractions in the permissionlesstech/bitchat repository.**

The permissionlesstech/bitchat repository ships a complete hardware-free test harness for its Bluetooth Low Energy mesh stack. Because the **link-layer is abstracted behind `BLELinkEvent` and `BLELinkCommand` ports**, you can replace the real CoreBluetooth-backed `BLELinkLayer` with a simulated implementation that drives the mesh engine through the exact same code paths used in production. This design makes it possible to run unit tests, integration tests, and property tests entirely in memory.

## How Hardware-Free Testing Works in BitChat

### The Three-Layer BLE Architecture

As documented in [`docs/BLE-ARCHITECTURE-V3.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/BLE-ARCHITECTURE-V3.md), the BitChat stack splits BLE functionality into three logical layers. The **link-layer** interfaces with CoreBluetooth, the **serial mesh engine** owns all protocol state, and feature modules sit on top. The critical testing hook is that the link-layer communicates with the engine through well-defined ports—`BLELinkEvent` and `BLELinkCommand`—so swapping the real radio for a fake one does not change the engine's behavior.

### Simulated Link-Layer and Concurrency Contracts

The architecture introduces a **simulated link-layer** via components like `BLEEngineManualScheduler` and the conceptual `SimulatedLinkLayer`. When tests use these, the engine still receives `BLELinkEvent` instances from a simulated source and respects the same concurrency contracts—specifically `bleQueue`-confined work handed off to `engine`-confined processing—that the production app enforces.

## Core Components for Testing the BitChat BLE Transport

- **`MockBLEService`** — An in-memory BLE service that registers on a global `MockBLEBus`, tracks connected peers, and routes packets. It mirrors the public API of the real `BLEService`, including `startServices()`, `sendMessage()`, and `sendPrivateMessage()`, so production code runs unchanged. Defined in [`bitchatTests/Mocks/MockBLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Mocks/MockBLEService.swift).
- **`MockBLEBus`** — A global bus that holds a registry of `MockBLEService` instances and an adjacency map describing which peers are "connected."
- **`BLEEngineManualScheduler`** — A deterministic scheduler for advanced tests that need to manually step pending timers or queues. Lives in [`bitchat/Services/BLE/BLEEngineManualScheduler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEEngineManualScheduler.swift).
- **Test suites** — Files such as [`bitchatTests/BLEServiceTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/BLEServiceTests.swift) and [`bitchatTests/BLEServiceCoreTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/BLEServiceCoreTests.swift) instantiate these mocks to exercise peer discovery, message flooding, private-media transfers, and noise session establishment.

## Simulating Mesh Behavior Without Radios

### Peer Discovery and Connection

Tests call `simulateConnectedPeer(_:)` or `simulateConnection(with:)` to establish links, and `simulateDisconnectedPeer(_:)` to tear them down. The bus updates its adjacency map and notifies delegates through `didConnectToPeer` and `didUpdatePeerList`, just as real Bluetooth discovery would.

### Message Routing and Flooding

When a mock service calls `sendMessage` or `sendPrivateMessage`, it builds a `BitchatPacket` and delivers it locally (echo) while propagating it to neighbors via the bus. The mock tracks `seenMessageIDs` to prevent routing loops, reproducing the production mesh's flood-control and de-duplication behavior.

### Deterministic Execution

Because there is no physical radio, there are no wall-clock waits. The entire mesh runs synchronously, allowing the full test suite to complete in under two seconds.

## Code Examples: Testing BitChat BLE Transport in Memory

### Setting Up a Two-Node Mesh

```swift
import bitchatTests

// Create a shared bus that will simulate the radio environment
let bus = MockBLEBus()

// Instantiate two mock services (they behave like real BLEService)
let alice = MockBLEService(bus: bus)
let bob   = MockBLEService(bus: bus)

// Give them human-readable nicknames (optional)
alice.mockNickname = "Alice"
bob.mockNickname   = "Bob"

// Connect the peers – this updates the adjacency map in the bus
alice.simulateConnection(with: bob)

// Attach a delegate so we can see received messages
alice.delegate = TestDelegate()
bob.delegate   = TestDelegate()

```

### Broadcasting a Public Message

```swift
// Alice sends a public message – the bus will forward it to Bob
alice.sendMessage("Hello from Alice!")

// Both delegates receive the same BitchatMessage instance
// (the mock echoes locally before forwarding, matching production behaviour)

```

### Sending a Private Direct Message

```swift
// Alice sends a private message to Bob's PeerID
alice.sendPrivateMessage(
    "Secret for Bob",
    to: bob.peerID,
    recipientNickname: "Bob",
    messageID: UUID().uuidString
)

// The bus delivers the packet only to Bob because they are direct neighbors.
// No other peers would see the payload.

```

### Simulating Topology Changes

```swift
// Disconnect the peers – further messages will not be forwarded
alice.simulateDisconnectedPeer(bob.peerID)

// Re-connect later if needed
alice.simulateConnectedPeer(bob.peerID)

```

### Advanced Timing with the Manual Scheduler

```swift
let scheduler = BLEEngineManualScheduler()
let alice = MockBLEService(bus: scheduler.bus)
let bob   = MockBLEService(bus: scheduler.bus)

// … connect, send messages, then manually step the scheduler:
scheduler.advance(by: .seconds(1))   // drives any pending timers or queues

```

## Key Source Files for BLE Transport Testing

- **[`docs/BLE-ARCHITECTURE-V3.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/BLE-ARCHITECTURE-V3.md)** — High-level design of the BLE stack and the simulated link-layer extraction.
- **[`bitchatTests/Mocks/MockBLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Mocks/MockBLEService.swift)** — In-memory BLE service used by all BLE-related tests.
- **[`bitchatTests/BLEServiceTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/BLEServiceTests.swift)** — Example test suite driving mesh scenarios with `MockBLEService`.
- **[`bitchat/Services/BLE/BLEEngineManualScheduler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEEngineManualScheduler.swift)** — Deterministic scheduler for low-level timing and back-pressure tests.
- **[`bitchat/Services/BLE/BLELinkLayer.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLELinkLayer.swift)** — The real CoreBluetooth-backed implementation that the mock replaces in tests.

## Summary

- BitChat's BLE transport is fully testable without Bluetooth hardware thanks to link-layer abstraction.
- `MockBLEService` and `MockBLEBus` provide an in-memory mesh that mirrors the production `BLEService` API.
- Tests exercise identical engine code paths, concurrency contracts, and packet routing logic used by the real app.
- The deterministic test environment runs synchronously and completes in under two seconds.
- Advanced scenarios use `BLEEngineManualScheduler` to step timers and verify back-pressure behavior.

## Frequently Asked Questions

### Can I run integration tests for the BitChat BLE mesh on an iOS Simulator?

Yes. Because `MockBLEService` replaces the CoreBluetooth-dependent `BLELinkLayer`, you can run the full suite—including peer discovery, message flooding, and noise session establishment—on an iOS Simulator or any Swift test runner without a physical Bluetooth adapter.

### Does the mock service use the same packet parsing and routing as production?

Yes. The mock builds real `BitchatPacket` instances and passes them through the same parsing, inbound/outbound buffers, and routing policies that the production `BLEService` uses. The only difference is that packets travel over `MockBLEBus` instead of a CoreBluetooth radio.

### How does the mock prevent infinite loops during message flooding?

`MockBLEService` tracks `seenMessageIDs` and respects the same flood-control rules as the production link-layer, ensuring packets are not forwarded repeatedly to the same peers or around routing loops.

### Is the hardware-free test suite fast enough for CI pipelines?

Yes. The absence of real radio initialization and wall-clock delays means the entire BLE-related test suite typically executes in less than two seconds, making it ideal for fast feedback in continuous integration environments.