Simulate Bluetooth Low Energy devices on Apple platforms — no hardware required.
Faketooth uses Objective-C runtime swizzling to replace CBCentralManager and CBPeripheral methods with simulated implementations, letting you create virtual peripherals with custom services, characteristics, descriptors, and advertisement data. Your existing CoreBluetooth code works unchanged — just set simulatedPeripherals and go.
Add the following dependency to your Package.swift:
dependencies: [
.package(url: "https://github.com/rozd/faketooth.git", from: "0.5.0")
]import FaketoothAssign an array of FaketoothPeripheral instances to CBCentralManager.simulatedPeripherals. Each peripheral represents a virtual BLE device with its own identifier, name, services, and advertisement data.
CBCentralManager.simulatedPeripherals = [
FaketoothPeripheral(
identifier: UUID(),
name: "Test",
services: [
FaketoothService(
uuid: CBUUID(),
isPrimary: true,
characteristics: [
FaketoothCharacteristic(
uuid: CBUUID(),
properties: [.read, .notify, .write],
descriptors: [
FaketoothDescriptor(
uuid: CBUUID(string: "2902"),
valueProducer: { () -> Any? in
return Data(capacity: 2)
}
)
],
valueProducer: { "Hello".data(using: .utf8) },
valueHandler: { data in
print("\(String(data: data!, encoding: .utf8)!)")
}
)
]
)
],
advertisementData: [
CBAdvertisementDataLocalNameKey: "Name for Advertisement"
]
)
]Build and run your project. Faketooth intercepts CBCentralManager calls transparently — scanning, connecting, service discovery, reads, writes, and notifications all work against your virtual peripherals.
When simulation is active, Faketooth:
- Fires
centralManagerDidUpdateState:with.poweredOnon first interaction - Filters
scanForPeripherals(withServices:)results by advertised service UUIDs - Filters
discoverServices:anddiscoverCharacteristics:forService:by UUID when non-nil - Supports
retrieveConnectedPeripherals(withServices:)for finding connected simulated peripherals
Set optional error properties to test your app's error-handling code paths. When an error is set, the corresponding delegate callback fires with the error and the operation does not mutate state (matching real CoreBluetooth behavior).
// Simulate a connection failure
peripheral.connectionError = NSError(
domain: CBErrorDomain,
code: CBError.connectionFailed.rawValue,
userInfo: nil
)
// centralManager(_:didFailToConnect:error:) will be called instead of didConnect
// Simulate a characteristic read error
characteristic.readError = NSError(
domain: CBATTErrorDomain,
code: CBATTError.readNotPermitted.rawValue,
userInfo: nil
)
// peripheral(_:didUpdateValueFor:error:) will receive the error
// Clear the error to restore normal behavior
peripheral.connectionError = nil
characteristic.readError = nilAvailable error properties:
FaketoothPeripheral:connectionError,disconnectionError,discoverServicesError,discoverCharacteristicsError,discoverDescriptorsErrorFaketoothCharacteristic:readError,writeErrorFaketoothDescriptor:readError,writeError
Set simulatedPeripherals to nil to restore real CoreBluetooth behavior:
CBCentralManager.simulatedPeripherals = nilFaketooth swizzles eight CBCentralManager methods in +load. Each swizzled method checks whether simulation is active — if simulatedPeripherals is nil, it falls through to the original CoreBluetooth implementation. This means Faketooth is completely inert unless explicitly activated.
| Class | Role |
|---|---|
CBCentralManager+Faketooth |
Swizzles scanning, connecting, retrieval, state, isScanning |
FaketoothPeripheral |
Overrides service discovery, read/write, notifications; supports error injection |
FaketoothService |
Holds characteristics, links back to peripheral |
FaketoothCharacteristic |
Supports valueProducer (read), valueHandler (write), and error injection |
FaketoothDescriptor |
Supports valueProducer, valueHandler, and error injection |
FaketoothSettings |
Configurable delays per operation via FaketoothDelaySettings |
The repository includes examples demonstrating different use cases. See the Demo directory.
Contributions are welcome! If you encounter issues, have questions, or want to suggest improvements, please open an issue.
Faketooth is available under the MIT license. See the LICENSE file for more information.