NordicSemiconductor / IOS-CoreBluetooth-Mock

Mocking library for CoreBluetooth framework.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Introduce a way to provide own impl of CBMCentralManager

philips77 opened this issue · comments

As @evnik suggested in #54 (comment) there should be a way to provide own implementation for CBMCentralManager. After #54 has been merged to develop it was refactored from a protocol to a class.

Questions:

  1. The new implementation is an open class, so it should be possible to extend it. It the protocol required?
  2. What name should it have?
  1. The new implementation is an open class, so it should be possible to extend it. It the protocol required?

CBMCentralManager is defined as a subclass of NSObject and its initializer is internal. This already put some restrictions on ability to subclass it and it's easy to miss if more restrictions are accidentally added in the future.

  1. What name should it have?

I'd prefer the protocol to be named as CBMCentralManager (so it minimizes possible changes in our code) and the class might be named as CBMBaseCentralManager/CBMCentralManagerBase. Personally I'd remove the CBM prefix from everywhere, but it looks like out of scope for now.

One of the goals when designing the library was to make the transitioning from CoreBluetooth to CoreBluetoothMock as seamless as possible. That's why we created set of type aliases so the code doesn't need to be modified, just imports. Or "M" added in the second option. In #54 the breaking change was to fulfill the same goal when using type methods in CBMCentralManager. They couldn't be called on a protocol. Therefore we'd like to keep the change. But with introducing a base protocol and modifying type aliases you may achieve what you need, i think.

I can easily make the init open as well and w can work together to make the API open as well.

Personally I'd remove the CBM prefix from everywhere, but it looks like out of scope for now.

I think it's not bad for libraries to have prefixes, a specially on common names, to avoid collisions. That on addition to what I wrote above.

May I ask about your CBMCentralManager implementation? Perhaps some things could be shared? Why did you choose to create your own?

May I ask about your CBMCentralManager implementation? Perhaps some things could be shared? Why did you choose to create your own?

Existing CBMCentralManagerMock mimics the CBCentralManager too much, for example initializers call the initialize() method (and we cannot override this), which adds a 10 ms pause in our tests and we should wait for it to finish before we can fetch peripherals. It's much simpler to define a base class with public writable properties and failing on any unexpected call:

class CentralManagerMock: CBMCentralManager, Mock {
    weak var delegate: CBMCentralManagerDelegate?
    var isScanning = false
    var state: CBMManagerState = .poweredOn
    var peripherals = [CBMPeripheral]()

   func retrievePeripherals(withIdentifiers identifiers: [UUID]) -> [CBMPeripheral] {
        if peripherals.isEmpty {
            fail()
        }

        return peripherals.filter { identifiers.contains($0.identifier) }
    }

    // the similar implementation for other methods
}

Then in a test we can just setup properties or subclass it and override only related methods to have a mocked instance.

I think it's not bad for libraries to have prefixes, a specially on common names, to avoid collisions.

In Swift that's not needed, in case of collision you can specify the namespace:

let mock = CoreBluetoothMock.CentralManager()
typealias CBMCentralManager = CoreBluetoothMock.CentralManager

Or do you need to keep it compatible with Objective-C?

That's easy to forget. It's like missing self. It could be required, as omitting may cause errors.

I'll think about what you wrote above. Perhaps we can have a simple and "exact" mock.

I removed the 10ms delay, but initialization of mock central manager is still asynchronous.
CBMCentralManager is not open, and you should continue extending it if you want.

I was experimenting with simplifying the mock, or making a simple mode, but that caused a lot of redundant code (for normal and simple mode), so at the end I removed it. Finally, many calls are asynchronous, but when peripheralSpec.connectionInterval is 0, the calls should be very quick, so at least no artificial delays.

I also kept the CBM prefix as well. It follows the CB prefix of the native API.