6

I would like to unit test a class that acts as a CBPeripheralManagerDelegate to the CBPeripheralManager class. Typically, in order to stub out an external class dependency, I would use either a form of dependency injection by passing in via the class initializer or via a property. When dealing with singleton-based API's, I have been able to use libraries like Kiwi to stub the class level method that returns the singleton (i.e. [ClassName stub:@selector(sharedInstance) andReturn:myStubbedInstance]). The issue in the case of mocking CBPeripheralManager is that its initializer takes the delegate instance. So any code that uses my class would need to do something like this:

PeripheralManagerWrapper *wrapper = [[PeripheralManagerWrapper alloc] init];
CBPeripheralManager *peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:wrapper queue:nil options:nil];
wrapper.peripheralManager = peripheralManager;

Then, for unit testing my PeripheralManagerWrapper class, I could simply instantiate it and pass in a mocked CBPeripheralManager. However, I don't like requiring any calling code of my wrapper object to have to go through this setup. Is there a better pattern for dealing with this situation? I've used both Kiwi and OCMockito, but neither seem to provide this functionality short of maybe stubbing the alloc and init methods of CBPeripheralManager and then just instantiating the instance in the PeripheralManagerWrapper 's initializer.

Michael McGuire
  • 3,770
  • 2
  • 29
  • 28

1 Answers1

4

IMHO, the Core Bluetooth APIs are perfect match for unit testing. All the delegate callbacks take the manager and the relevant parameters so if you follow the pattern that you use these arguments instead of the internal state, then you will be able to pass in anything you want. Using mock objects is the best way to do this. While unit testing, you shouldn't try to mock the behavior of the managers. You should focus on verifying the interaction of your code with the API and nothing more.

Wrappers may better suit integration testing. But actually, the integration testing of Core Bluetooth code is better done manually to my experience. The stack is not stable enough to allow for reliable testing, and the test code would have to be fortified against the stack errors too which is really hard as, obviously, those are not documented or predictable just by looking at the APIs. While on the other hand, your test code would have to simulate the erroneous behavior of the stack too. There may be cases when it is possible but the test code will be just as complex if not more than the code you are testing.

allprog
  • 16,540
  • 9
  • 56
  • 97
  • Yeah, the wrapper was not really about testing, but more for isolating the Bluetooth behavior away from the ViewController. Wrapper may have not been the best suffix. – Michael McGuire Jan 28 '14 at 17:32
  • Good. It's always good practice to separate your business logic from the 3rd party APIs. Did I answer your question? – allprog Jan 28 '14 at 20:19
  • 1
    @allprog Additional Q: I was about to test delegates of CBCentralManager and CBPeripheral, etc. But CBPeripheral/CBService/CBCharacteristic has hidden inits and so it cannot be instantiated. Is it then possible to mock it? (BTW I am using Swift - will this be an additional challenge?) Thanks :) – Jens Schwarzer Jul 22 '15 at 12:56
  • 1
    @JensSchwarzer Some example code that shows this is possible in ObjC is listed [here](https://github.com/beepscore/BLELister/blob/master/BLEListerTests/BSDetailViewControllerTests.m). Unfortunately, I do not have enough experience with Swift but searches do not pop any solution (I guess you asked for this reason, too :) ). Separating the business logic out of the CB domain is probably the solution, though. This way you would use your own types, CB limitations would not restrict. It may even be possible that there is no other way to unit test CB code in swift. I'm sorry that I cannot help more. – allprog Jul 24 '15 at 08:27
  • Thanks a lot for the example code @allprog - very nice of you! OK it seems to be possible to do it ObjC. I might be that I have to write my tests in ObjC then :S – Jens Schwarzer Jul 24 '15 at 09:44
  • Link in a comment is dead now, sadly. Always prefer code examples than links for this reason. :/ – Kane Cheshire Feb 02 '19 at 16:35
  • @KaneCheshire [New link](https://github.com/beepscore/BLELister/blob/383a4e099b1c1d7b0f3f848fca16cfb825533e57/BLEListerTests/DetailViewControllerTests.m) I guess the full example hardly fits in a comment. :) – allprog Feb 05 '19 at 15:28
  • 1
    That's what answers are for I guess – Kane Cheshire Feb 05 '19 at 17:45
  • @KaneCheshire sure, this question has been answered in a textual form. Not an elaborate example and takes quite a bit of domain knowledge to understand it. Worked out well for the OP but improvements are always welcome. – allprog Mar 18 '19 at 11:53
  • 1
    Yet another link here. It's an old question, but maybe this link will help someone. It's got useful recommendations, if you can get past the emoticons. [Unit Test All Things](https://swifting.io/blog/2016/06/06/17-unit-test-all-the-things/) – Victor Engel Sep 06 '20 at 14:14