1

I found this thread as a reference: How do I wait for an asynchronously dispatched block to finish? and am using that code (signaling the semaphore inside the block) to wait on my thread to finish before finishing the test.

However my semaphore never signals that its done... so I stepped through the code and found that the code inside this little snippet never runs:

        dispatch_async(dispatch_get_main_queue(), ^{
            [self removeDownloadSpinner];
            block(callback);
        });

Does dispatch_get_main_queue() not fire when I am unit testing? If not, how do you unit test async blocks?

EDIT (Here is a little more code): Basically I have a "ServerRequest" class that contains all the incoming/outgoing network requests for the app. One method is this (completion block is typedeffed):

+(void)checkPlayerCode:(NSString *)playerCode completionHandler:(PlayerCompletionBlock)block {
  [self addDownloadSpinner];
  NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/players/%@",kBaseURL, playerCode]];
  NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
  [ServerConnectionRequest sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
    NSDictionary *p = [data objectFromJSONData];
    Player *player = [[Player alloc] init];
    player.last_name = [p objectForKey:@"last_name"];
    if (!error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self removeDownloadSpinner];
            block(player);
        });

    }
}]; 

}

Which is called from a uiviewcontroller like this:

    [ServerRequest checkPlayerCode:codeCell.textField.text completionHandler:^(Player *p){
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"wooo block" message:@"works" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles: nil];
    [alert show];
}];

However the dispatch_get_main_queue() block never gets fired.

Community
  • 1
  • 1
Msencenb
  • 5,407
  • 11
  • 48
  • 81

2 Answers2

1

There is no problem doing multithreaded code within unit tests and I've run all sorts of dispatch code in them.

When you say stepped through the code do you mean that you literally stepped through using the debugger. If so and you assumed that the code has not run because the debugger didn't stop in the block, then you have to remember this would be running on another thread and you will need a break point to get it to stop.

So I would be assigning break points within the block.

Secondly if you test code is running from the main thread then this will not work because you cannot async execute some code on the same thread you are on. The unit test code should be running on a background thread.

Then there is the issue of execution timing. Because you are running an async block it simply may be that it has not executed yet.

Testing of async calls means that it's likely that you code above will have to wait for the async call to finish before it can assert anything it may have done. In which case you have to consider it should be a sync instead of an async.

You need to show more code I think :-)

drekka
  • 18,723
  • 12
  • 67
  • 112
  • So I added in more code of the actual blocks. And yes I mean I literally stepped through the debugger. The background thread fires off... but a break inside of the callback block never gets hit. Also thought the issue might be that the test is running on the main thread; however I just tried running the test on a background thread (gcd priority default) and it still isn't working. Should I post my test as well? – Msencenb Mar 23 '12 at 09:44
  • Think I'm getting closer. Problem is that my semaphore is locking the main thread so the other callback can never get the thread. However if I move the semaphore_wait call into an async thread the unit test doesn't wait for it and just completes. Any chance you can give me an example of a unit test that uses a semaphore to wait without blocking the main thread? – Msencenb Mar 23 '12 at 09:52
  • Don't have any on me. I did some code with a semaphore a few weeks ago and then refactored it out. But as you say, you have to watch out for locking the thread you are trying to execute on. Also as I suggested to someone else recently, take a look at GCD and Operations. They may be an easier and better answer to the larger problem you are trying to solve. – drekka Mar 23 '12 at 13:41
1

The problem is that you are waiting on the semaphore on the main thread and simultaneously are trying to execute some code on the main tread with dispatch_async(dispatch_get_main_queue(), ...). Try to use NSRunLoop's runUntilDate, like

__block volatile bool loadComplete = false;
dispatch_async(dispatch_get_main_queue(), ^(..)
                                                {                                                                     
                                                    ..
                                                    loadComplete = true;
                                                });

NSDate* startTime = [NSDate date];
while ( !loadComplete ) 
{
    NSDate* nextTry = [NSDate dateWithTimeIntervalSinceNow:0.1];
    [[NSRunLoop currentRunLoop] runUntilDate:nextTry]; 

    if ( [nextTry timeIntervalSinceDate:startTime] > ../* some appropriate time interval here */ )
        STAssertTrue(false, @"TIMEOUT");
}

// Assertions here
fspirit
  • 2,437
  • 2
  • 18
  • 26