Problems

I guess that you already saw some C/C++ functions like these get called in Objective-C :]

int status;
change_network_status(status)

or even GCD:

dispatch_async(queue, ^{
    //...
});

The problem here is how you can make sure that the C/C++ functions or the block of dispatch_async get called? How we can verify it by using XCTestCase?

Objective-C

What you need to do is passing a pointer to a function!

Let’s get started with a simple class call Service, and inside this class I need to call a C/C++ function to start my services via a library.

My C/C++ function need an input is the state of the app (active, inactive, background), and it will return a flag which indicate whether start my services successfully or not.

int start_platform_service(int state);

In the Service.m file I have something like this:

@interface Service: NSObject
@end

@implementation Service 

- (void)startService {
    int state = 1; 
    int result = start_platform_service(state);
    if (result == 1) {
        NSLog("Success");
        //...
    } else {
        NSLog("Failure");
        //...
    }
}

@end

And the ServiceTest.m file will be:

@interface ServiceTest: XCTestCase
@end

@implementation ServiceTest 

- (void)testStartService {
    //... how to verify this method gets trigger `start_platform_service` here?
}

@end

The question now is how we can verify that the start_platform_service get called when we run startService?

We need to passing a pointer to method start_platform_service to treat it like a dependency and then we easily to mock it.

Back to my Service.m, I will declare a property as a pointer and named it as: startPlatformService

@interface Service: NSObject
@property (nonatomic, assign) int (*startPlatformService)(int state);
@end

Then we need to update our implementation to use this pointer:

  • In initialise method we need to assign which C/C++ function we want to point to our property.
  • Then we call our pointer property instead of C/C++ function.

We’ll have something like this:

@implementation Service 

- (instancetype)init {
    self = [super init];
    if (self) {
        _startPlatformService = &start_platform_service;
    }
    return self;
}

- (void)startService {
    int state = 1; 
    int result = self.startPlatformService(state);
    if (result == 1) {
        NSLog("Success");
        //...
    } else {
        NSLog("Failure");
        //...
    }
}

@end

Testing:

My aming to mock the C/C++ function, now let’s create a mock method for it, and point our pointer property to that mocking method.

@implementation ServiceTest 

int start_platform_service_mock(int state) {
    //...
    return 0;
}

@end 

To make sure our mock method get called, we need to create static variable to verify this method:

@implementation ServiceTest 

static BOOL start_platform_service_mock_get_called = NO;
int start_platform_service_mock(int state) {
    start_platform_service_mock_get_called = YES;
    return 0;
}

@end

Now we’re able to test our method startService:

@interface ServiceTest: XCTestCase
@end

@implementation ServiceTest 

static int start_platform_service_result_mock = -1;
int start_platform_service_mock(int state) {
    return start_platform_service_result_mock;
}

- (void)testStartService {
    //Given:
    start_platform_service_mock_get_called = NO;
    Service *service = [[Service alloc] init];
    service.startPlatformService = &start_platform_service_mock;
	
    //When:
    [service startService];
	
    //Then:
    XCTAssertTrue(start_platform_service_mock_get_called);
}

@end

Hope it will help you guys a bit in your objc developmemt life!