Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking Cocoa objects with unavailable initializers in unit tests

I am building an iOS app with Swift that uses apple's CoreBluetooth framework to communicate with peripherals via Bluetooth Low Energy. In the effort of unit testing a custom controller which implements the CBCentralManagerDelegate and CBPeripheralDelegate protocols, I am providing a test double subclassed from CBCentralManager to mimic the behaviour of the CoreBluetooth API. The delegate callbacks of the controller are invoked at appropriate times.

So far this has been working pretty well. But when it comes to calling the CBPeripheralDelegate callbacks, a CBPeripheral needs to be passed in. Usually, I would just fake the component providing my own subclass or mock an instance of CBPeripheral.

Here's the catch: The designated initializer of CBPeripheral in the underlying Objective-C library is marked as unavailable which prevents me from instantiating a CBPeripheral in my faked CBCentralManager and passing it as an argument to the CBPeripheralDelegate under test. (XCode leaves me with a compiler error.)

So far I have tried providing a custom initializer in my fake subclass (which doesn't work because I have to call the unavailable designated init eventually), overriding the initializer with an extension (which is not allowed apparently) and calling the selector (init) with the Objective-C runtime like I don't give a shit that it's unavailable. The last approach seems the most promising, but isn't really working and leaves me with unmanaged objects of the wrong type (my test class).

I've definitely hit a roadblock here and would be extremely glad for any input on

  1. instantiating a CBPeripheral despite init() being unavailable

  2. a different approach for unit testing the CBPeripheralDelegate

like image 721
thyrel Avatar asked Mar 09 '16 17:03

thyrel


1 Answers

I managed to solve the problem myself by pursuing the objc-runtime idea and creating an Object Factory in Objective-C, that calls the initializer's implementation by its pointer to ignore the unavailable mark.

For further information see: http://ijoshsmith.com/2014/06/05/instantiating-classes-by-name-in-swift/ (This is not working anymore in current Swift but can be easily adapted)

like image 186
thyrel Avatar answered Oct 30 '22 02:10

thyrel