How does one unit test code that interacts with the Core Bluetooth APIs?
Asked Answered
U

1

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.

Uniliteral answered 24/1, 2014 at 15:40 Comment(0)
K
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.

Kunz answered 25/1, 2014 at 9:0 Comment(10)
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.Uniliteral
Good. It's always good practice to separate your business logic from the 3rd party APIs. Did I answer your question?Kunz
@Kunz 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 :)Corneous
@JensSchwarzer Some example code that shows this is possible in ObjC is listed here. 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.Kunz
Thanks a lot for the example code @Kunz - 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 :SCorneous
Link in a comment is dead now, sadly. Always prefer code examples than links for this reason. :/Geber
@KaneCheshire New link I guess the full example hardly fits in a comment. :)Kunz
That's what answers are for I guessGeber
@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.Kunz
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 ThingsSinter

© 2022 - 2024 — McMap. All rights reserved.