Rightpoint / RZBluetooth

Core Bluetooth helper library
Other
136 stars 47 forks source link

RZBEnableMock() doesn't work in Swift #80

Closed cpatterson-lilly closed 5 years ago

cpatterson-lilly commented 6 years ago

It seems that RZBEnableMock() doesn't work when used in pure Swift code.

This came up while we were writing some peripheral simulator unit tests.

Try pasting the following code into the file SwiftTestCase.swift:

class SwiftTestCase2: XCTestCase {

    func testMocking() {
        var centralManager = CBCentralManager()
        XCTAssertNil(centralManager.mock)

        var peripheralManager = CBPeripheralManager()
        XCTAssertNil(peripheralManager.mock)

        // Enable RZBluetooth mocking
        RZBEnableMock(true)

        centralManager = CBCentralManager()
        XCTAssertNotNil(centralManager.mock) // Fails

        peripheralManager = CBPeripheralManager()
        XCTAssertNotNil(peripheralManager.mock) // Fails

        // Restore normal operation
        RZBEnableMock(false)

        centralManager = CBCentralManager()
        XCTAssertNil(centralManager.mock)

        peripheralManager = CBPeripheralManager()
        XCTAssertNil(peripheralManager.mock)
    }
}

Apparently, Swift's static typing isn't fooled by swizzling the Obj-C alloc method...

mplorentz commented 6 years ago

I have been able to work around this issue by creating an Objective-C factory class that has functions to instantiate the Core Bluetooth classes that I need. I call this factory class from my Swift code. It's no pretty but it works.

KingOfBrian commented 6 years ago

Yea, there's not much we can do here. It might make sense to add some factory methods to RZBluetooth so the fix is easy at the least. Any thoughts?

cpatterson-lilly commented 6 years ago

How about replacing RZBEnableMock() with an RZBMocking class that wraps closures with mocked objects. Something like Data.withUnsafeBytes(), etc.

Something like this?

Swift:

@objc class RZBMocking {
    public static func withMockCentralManager(_ block: (CBCentralManager)->()) {
    }
    public static func withMockPeripheralManager(_ block: (CBPeripheralManager)->()) {
    }
}

Obj-C:

typedef void (^MockedCentralManagerBlock)(CBCentralManager * centralManager);
typedef void (^MockedPeripheralManagerBlock)(CBPeripheralManager * peripheralManager);

@interface RZBMocking
+ (void)withMockCentralManager:(MockedCentralManagerBlock)block;
+ (void)withMockPeripheralManager:(MockedPeripheralManagerBlock)block;
@end
KingOfBrian commented 5 years ago

I'm going to add a make() method to work around this. I'm defining it in RZBMockFramework -- would that of made it available in your use cases, or does it want to be in RZBluetooth?

cpatterson-lilly commented 5 years ago

I assume you mean a class method called make() on the RZBMockPeripheralManager and RZBMockCentralManager classes, which live in the RZBMockFramework? I see no problems with that solution.

KingOfBrian commented 5 years ago

It's actually implemented in CBCentralManager so it isolates knowledge of the mocks. See #95.