Swiftpack.co -  NordicSemiconductor/IOS-CoreBluetooth-Mock as Swift Package
Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
NordicSemiconductor/IOS-CoreBluetooth-Mock
Mocking library for CoreBluetooth framework.
.package(url: "https://github.com/NordicSemiconductor/IOS-CoreBluetooth-Mock.git", from: "0.13.0")

Core Bluetooth Mock

Version number Platform Carthage compatible

The Core Bluetooth Mock library was designed to emulate Core Bluetooth objects, providing easy way to test Bluetooth-enabled apps. As the native Bluetooth API is not supported on a simulator, using this library you can run, test and take screenshots of such apps without the need of a physical phone or tablet. You may also start working on the iOS app when your peripheral is still under development.

Core Bluetooth?

The Core Bluetooth framework provides the classes needed for your apps to communicate with Bluetooth-equipped low energy (LE) wireless technology. It requires an iPhone or iPad to work making Bluetooth-enabled apps difficult to test. As the documentation states:

Don’t subclass any of the classes of the Core Bluetooth framework. Overriding these classes isn’t supported and results in undefined behavior.

Core Bluetooth Mock!

The Core Bluetooth Mock library defines number of CBM... classes and constants, that wrap or imitate the corresponding CB... counterparts from Core Bluetooth framework. For example, CBMCentralManager has the same API and behavior as CBCentralManager, etc. On physical iDevices all calls to CBMCentralManager and CBMPeripheral are forwarded to their native equivalents, but on a simulator a user defined mock implementation is used.

How to start

The Core Bluetooth Mock library is available only in Swift, and compatible with iOS 9.0+, macOS 10.13+, tvOS 9.0+ and watchOS 2.0+, with some features available only on newer platforms. For projects running Objective-C we recommend https://github.com/Rightpoint/RZBluetooth library.

Including the library

The library support CocoaPods, Carthage and Swift Package Manager.

CocoaPods

  • Create/Update your Podfile with the following contents

    target 'YourAppTargetName' do
        pod 'CoreBluetoothMock'
    end
    
  • Install dependencies

    pod install
    
  • Open the newly created .xcworkspace

Carthage

  • Create a new Cartfile in your project's root with the following contents

    github "https://github.com/NordicSemiconductor/IOS-CoreBluetooth-Mock" ~> x.y // Replace x.y with your required version
    
  • Build with carthage

    carthage update --platform iOS // also supported are tvOS, watchOS and macOS
    
  • Copy the CoreBluetoothMock.framework from Carthage/Build to your project and follow instructions from Carthage.

Swift Package Manager

The library can also be included as SPM package. Simply add it in Xcode: File -> Swift Packages -> Add package dependency, type https://github.com/NordicSemiconductor/IOS-CoreBluetooth-Mock.git and set required version, branch or commit.

If you have Swift.package file, inculde the following dependency:

dependencies: [
    // [...]
    .package(name: "CoreBluetoothMock", 
             url: "https://github.com/NordicSemiconductor/IOS-CoreBluetooth-Mock.git", 
             .upToNextMajor(from: "x.y")) // Replace x.y with your required version
]

and add it to your target:

targets: [
    // [...]
    .target(
        name: "<Your target name>",
        dependencies: ["CoreBluetoothMock"]),
]

Usage

With this complete, you need to choose one of the following approaches:

Using aliases (recommended)

Copy CoreBluetoothTypeAliases.swift file to your project. It will create number of type aliases for all CBM... names and rename them to CB..., so you will not need to perform any changes in your code, except from removing import CoreBluetooth in all your files, as the types are now defined locally.

Direct

Replace import CoreBluetooth with import CoreBluetoothMock in your classes.

Replace all instances of CB... with CBM....

Other required changes

The only difference is how the central manager is instantiated. Instead of:

let manager = CBCentralManager(delegate: self, queue: ...)

you need to use the CBCentralManagerFactory:

let manager = CBCentralManagerFactory.initiate(delegate: self, queue: ...)

The last parameter, forceMock, when set to true, allows to run mock implementation also on a physical device.

Known issues

  • The new CBMPeripheral is a protocols, not a class. That means that, e.g. they cannot be used as Equatable or Hashable. When using in maps, you may need to use the identifier. Also, KVO are not yet supported.

Defining mock peripherals

When the app using Core Bluetooth Mock library is started on a simulator, or the forceMock parameter is set to true during instantiating a central manager instance, a mock version of central manager will be created. Use the following methods and properties to simulate central manager behavior:

Basic

CBMCentralManagerMock.simulateInitialState(_ state: CBMManagerState) - this method should be called before any central manager instance was created. It defines the intial state of the mock central manager. By default, the manager is powered off.

CBMCentralManager.simulatePowerOn() - turns on the mock central manager.

CBMCentralManager.simulatePowerOff() - turns off the mock central manager. All scans and connections will be terminated.

CBMCentralManagerMock.simulatePeripherals(_ peripherals: [CBMPeripheralSpec]) - defines list of mock peripheral. This method should be called when the manager is powered off, or before any central manager was initialized.

CBMCentralManagerMock.tearDownSimulation() - sets the state of all currently existing central managers to .unknown and clears the list of managers and peripherals bringing the mock manager to initial state.

See AppDelegate.swift for reference. In the sample app the mock implementation is used only in UI Tests, which lauch the app with mocking-enabled parameter (see here), but can be easily modified to use it every time it is launched on a simulator or a device.

Peripheral specs

CBMPeripheralSpec.Builder - use the builder to define your mock peripheral. Specify the proximity, whether it is advertising together with advertising data and advertising interval, is it connectable (or already connected when your app starts), by defining its services and their behavior. A list of such peripheral specifications needs to be set by calling the simulatePeripherals(:) method described above.

CBMPeripheralSpec.simulateConnection() - simulates a situation when another app on the iDevice connects to this peripheral. It will stop advertising (unless advertisingWhenConnected flag was set) and will be available using manager.retrieveConnectedPeripherals(withServices:).

CBMPeripheralSpec.simulateDisconnection(withError:) - simulates a connection error.

CBMPeripheralSpec.simulateReset() - simulates device hard reset. The central will notify delegates 4 seconds (supervision timeout) after the device has been reset.

CBMPeripheralSpec.simulateProximityChange(:) - simulates moving the peripheral close or away from the device.

CBMPeripheralSpec.simulateValueUpdate(:for:) - simulates sending a notification or indication from the device. All subscribed clients will be notified a connection interval later.

CBMPeripheralSpec.simulateCaching() - simulates caching the device by the iDevice. Caching pairs the device's MAC with a random identifier (UUID). A device is also cached whenever it is scanned. Caching makes the device available to be retrieved using CBMCentralManager.retrievePeripherals(withIdentifiers:).

CBMPeripheralSpec.simulateMacChange(:) - simulates the device changing its MAC address. The iDevice will not contain any cached information about the device, as with the new MAC it is considered to be a new device.

See AppDelegate.swift for reference, where 3 mock peripherals are defined: a test blinky device (like in Nordic SDK), an HRM device (GATT behavior not implemented, as the app does not support it), and a Physical Web Beacon, a non-connectable device. The 2 latter will not pop up on in the sample app, as it is scanning with Service UUID filter.

Advanced

CBMCentralManagerMock.simulateStateRestoration - this closure will be used when you initiate a central manager with CBMCentralManagerOptionRestoreIdentifierKey option. The map returned will be passed to centralManager(:willRestoreState:) callback in central manager's delegate.

CBMCentralManagerMock.simulateFeaturesSupport - this closure will be used to emulate Bluetooth features supported by the manager. It is availalbe on iOS 13+, tvOS 13+ or watchOS 6+.

CBMCentralManagerMock.simulateAuthorization(:) - Simulates the current authorization state of a Core Bluetooth manager. When any value other than .allowedAlways is returned, the CBMCentralManager will change state to CBMManagerState.unauthorized.

Sample application: nRF BLINKY

nRF Blinky is an example app targeted towards newcomer BLE developers, and also demonstrating the use of Core Bluetooth Mock library. This application controls an LED on an nRF5DK and receive notifications whenever the button on the kit is pressed and released.

The mock implementation is used in UI tests. See AppDelegate.swift and UITests.swift classes.

Nordic LED and Button Service

A simplified proprietary service by Nordic Semiconductor, containing two characteristics one to control LED3 and Button1:

  • Service UUID: 00001523-1212-EFDE-1523-785FEABCD123

    • First characteristic controls the LED state (On/Off).
      • UUID: 00001525-1212-EFDE-1523-785FEABCD123
      • Value: 1 => LED On
      • Value: 0 => LED Off
    • Second characteristic notifies central of the button state on change (Pressed/Released).
      • UUID: 00001524-1212-EFDE-1523-785FEABCD123
      • Value: 1 => Button Pressed
      • Value: 0 => Button Released

    For full specification, check out documentation.

Requirements:

  • An iOS device with BLE capabilities, or a simulator (to run the mock).
  • A Development Kit (unless testing mock).
  • The Blinky example firmware to flash on the Development Kit. For your conveninence, we have bundled two firmwares in this project under the Firmwares directory.
  • To get the latest firmwares and check the source code, you may go directly to our Developers website and download the SDK version you need, then you can find the source code and hex files to the blinky demo in the directory /examples/ble_peripheral/ble_app_blinky/
  • The LBS (LED Button Service) is also supported in nRF Connect SDK: here.
  • More information about the nRFBlinky example firmware can be found in the documentation.

Installation and usage:

  • Prepare your Development kit.

    • Plug in the Development Kit to your computer via USB.
    • Power On the Development Kit.
    • The Development Kit will now appear as a Mass storage device.
    • Drag (or copy/paste) the appropriate HEX file onto that new device.
    • The Development Kit LEDs will flash and it will disconnect and reconnect.
    • The Development Kit is now ready and flashed with the nRFBlinky example firmware.
  • Start Xcode and run build the project against your target iOS Device (Note: BLE is not available in the iOS simulator, so the iOS device is a requirement to test with real hardware).

    • Launch the nRF Blinky app on your iOS device.
    • The app will start scanning for nearby peripherals.
    • Select the Nordic_Blinky peripheral that appears on screen (Note: if the peripheral does not show up, ensure that it's powered on and functional).
    • Your iOS device will now connect to the peripheral and state is displayed on the screen.
    • Changing the value of the Toggle switch will turn LED 3 on or off.
    • Pressing Button 1 on the Development Kit will show the button state as Pressed on the app.
    • Releasing Button 1 will show the state as Released on the App.

GitHub

link
Stars: 97
Last commit: Yesterday

Ad: Job Offers

iOS Software Engineer @ Perry Street Software
Perry Street Software is Jack’d and SCRUFF. We are two of the world’s largest gay, bi, trans and queer social dating apps on iOS and Android. Our brands reach more than 20 million members worldwide so members can connect, meet and express themselves on a platform that prioritizes privacy and security. We invest heavily into SwiftUI and using Swift Packages to modularize the codebase.

Submit a free job ad (while I'm testing this). The analytics numbers for this website are here.

Release Notes

0.13.0
4 weeks ago

Version 0.13.0

The new version allows mocking more features present in the native API: authorization and support for duplicate services. Some changes broke the existing API so migration is necessary. Migration guide available below.

New features:

  • Support for authorization added (#54 which fixed #46).
  • Support for duplicate services (#62 which fixed #42).
  • Allowing extending CBMCentralManager (#56 which fixed #55). #54 changed the type from a protocol to a class so additional work was necessary to make it extendable.
  • Support for retrieving peripherals (#57 with #45).
  • Option to change mock device MAC address (also #57) making it not-retrievable.
  • Option to simulate caching device (again #57) to make a device retrievable without scanning.

Changes related to Xcode 13 beta:

  • Fixed build for Xcode 13 (#50, #51, #52) - Xcode 13 and Swift 5.5 changed type of peripheral and service properties in CBService and CBCharacteristic from unowned to weak making the properties optional. The change, just like in native Swift, applies only in Swift 5.5+.

Bugs fixed:

  • Unit test failing with EXC_BAD_ACCESS (#39 fixed with #60)
  • Fix incorrect range issue when writing data with size less than mtu - 3 (#43 fixed with #53)
  • Calling simulateValueUpdate for characteristic repeatedly can result in dropped and duplicate values updates (#59 fixed with #58)
  • Peripheral disconnect and teardown cause assertion failure (#25 perhaps fixed with #61)

Breaking changes

  1. CBMCentralManager was refactored from a protocol to a class. Extending the base class is still possible, as it was made open. This change allows using CBMCentralManager.authorization (#54) on the base class without the need to specify mock or native manager. See #56 and #55 for more information.
  2. Number of callback methods in CBMPeripheralSpecDelegate were refactored to receive the original mock attribute (CBMServiceMock, CBMCharacteristicMock and CBMDescriptorMock instead of per-client copies of the above. This makes the API more logical, and also allows having duplicated service, characteristics and descriptors (having another instance with the same UUID). The old methods are deprecated and are still called with the new parameters, but modifying the parameter type is recommended, as this may change in the future (it generates warnings).
  3. Both simulate... properties were moved from CBMCentralManagerFactory to CBMCentralManagerMock, where other such methods already were present. This brings similar methods to one place.

Migration guide from 0.12.1 to 0.13.0

  • Change the types of parameters in your CBMPeripheralSpecDelegate from CBMService, CBMCharacteristic and CBMDescriptor to CBMServiceMock, CBMCharacteristicMock and CBMDescriptorMock respectively.
  • If CBMCentralManagerFactory.simulateStateRestoration or CBMCentralManagerFactory.simulateFeaturesSupport properties were used, change CBMCentralManagerFactory type to CBMCentralManagerMock. The API and behavior are the same.
  • If you for some reason provided your own implementation of CBMCentralManager modify your code as the CBMCentralManager became a open class instead do a protocol.

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics