Leem.fin
Leem.fin

Reputation: 42602

Unit test a function which performs a selector and wait until its done

I have a MyService class which inherits NSThread:

header:

@interface MyService : NSThread {
  -(void) startMe;
  -(void) doTask;
  ...
}

implementation:

@implementation MyService
  -(void)startMe {
   [self start];
  }
  -(void) doTask {
    [self performSelector:@selector(checkData:) onThread:self withObject:nil waitUntilDone:YES];
  }

  -(void) checkData {
    ...
    // NOTE: dataChecked is an instance variable.
    dataChecked = YES;
  }
@end

I want to unit test the above -(void)doTask and verify that -(void)checkData is really called. I use OCMock library to partially mock MyService & tried the following way:

-(void) testCheckData {
  // partial mock MyService
  id myService = [OCMockObject partialMockForObject:[MyService getInstance]];
  [myService startMe];

  // function to test
  [myService doTask];

  // I wait 2 seconds for checkData to finish its work
  dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
    dispatch_after(delayTime, dispatch_get_main_queue(), ^(void){
        // When running this test case, these code is never called & test passed successfully, WHY?
        NSLog(@"Check if checkData does its work");
        XCTAssertTrue([self isDataChecked]);
    });
}

Two questions:

Q1. As you see above, in my test case, I call -(void)doTask & wait 2 seconds, expect the -(void)checkData is executed, then assert the result, but when running test case, it doesn't run the block in dispatch_after(...) & test passed successfully, WHY it doesn't run the block?

Q2. What is the right way to unit test a function like -(void)doTask which invokes NSObject#performSelector:onThread:withObject:waitUntilDone:?

Upvotes: 0

Views: 794

Answers (1)

Avi
Avi

Reputation: 7552

Q1

If you can't change doTask to make it testable, you have to use an XCTestExpectation to wait for the test to complete. Also, instead of waiting a preset amount of time, you should busy-loop, checking the variable on each iteration. You can set a timeout on the expectation to trigger a failure after some reasonable amount of time.

// setup expectation    
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    while (dataChecked == NO);
    // signal expectation
});

// wait for expectation

Q2

If you have to know the implementation of the method to successfully test it, it's not a good candidate for unit testing. It shouldn't matter how the method being tested invokes other methods. It only matters if side-effects are synchronous- or asynchronously completed.

Upvotes: 0

Related Questions