0

I have a custom view controller, on it I have a button when tapped calls a web service asynchronously using AFHTTPRequestOperation and if request return success , I push a uitableviewcontroller. This work perfectly on device and simulator. Now I want to write unit test. I am running sample unit test that xcode generates. In the unit test, I managed to get the reference to my custom uiviewcontroller via sharedapplication.delegate.window.rootviewcontroller. I can call button tapped function using performSelectorOnMainThread but success or error function of my web service request does not get fired. I call thread sleep after calling button tap event to wait still nothing fired. I suspect this is a threading issue. How can I debug this ?

cagatay
  • 477
  • 5
  • 16
  • possible duplicate of [IOS -NSRunLoop in XCTest: How Do I Get A Run Loop to Work in A Unit Test?](http://stackoverflow.com/questions/19522898/ios-nsrunloop-in-xctest-how-do-i-get-a-run-loop-to-work-in-a-unit-test) – Abizern Mar 13 '14 at 15:49
  • As an aside, you don't want to actually call a web service in your actual test code, because then you aren't using a controlled environment. You might want to look at mocking your web service calls. – Abizern Mar 13 '14 at 15:50
  • @Abizern The linked solution is not reliable. Please read comment below why. – CouchDeveloper Mar 13 '14 at 20:13
  • @Abizern I agree I should DI this. – cagatay Mar 14 '14 at 01:08

2 Answers2

1

Tim's post is the general idea behind what you need to do, but it deadlocks the main thread, which can cause havoc with some APIs.

A better way to do it is to wait but keep the main thread running. There is a great example here:

IOS -NSRunLoop in XCTest: How Do I Get A Run Loop to Work in A Unit Test?

Have some sort of condition that's set when you've handled your async's completion and set up your loop that spins the run loop to end and move on when this condition is met.

Note that you might want to have a maximum time that you wait on the async test. If your async test never completes (maybe there is an error in the underlying code), you'll deadlock on the test, and that would be bad. So note the starting time of the test, and if you've been waiting longer than a certain length of time past the start time, fail the test and move on.

Community
  • 1
  • 1
Colin Cornaby
  • 741
  • 4
  • 14
  • Colin, I will try this. My unit test has thread sleep to wait action finish, as you said I might be blocking main thread or what ever thread waiting for success event. – cagatay Mar 13 '14 at 15:26
  • @aziz Be carefully, this example won't work reliable: a run loop only returns when it has finished processing an event, or when its timeout expires. In the example, it never times out. Now, suppose your asynchronous task will never schedule a message on the main run loop with the exact same mode, then this run loop will never return. There are also potential data races with the flag. – CouchDeveloper Mar 13 '14 at 20:09
  • The possibility for deadlock can't really be avoided entirely as much as mitigated. The problem this approach tries to avoid is a lot of APIs sync back to the main thread to execute a task, but if your unit test has locked the main thread waiting for the task to complete, you'll get a deadlock. I tend to think this approach is better because it acknowledges a call might need the main thread, but you're right, it's still not perfect. – Colin Cornaby Mar 13 '14 at 21:38
  • Thanks Colin, this worked perfectly.Yes I am aware of the reliability issue. – cagatay Mar 14 '14 at 01:13
0

You're right that it's a threading issue. You pretty much have to keep your test "alive" until it either succeeds or fails.

Here's an example from my unit tests for my networking library.

SomeUnitTest.m

- (void)signalFinished:(NSCondition *)condition
{
    [condition lock];
    [condition signal];
    [condition unlock];
}

#pragma mark - GET

- (void)testGet
{
    __block NSCondition *completed = NSCondition.new;
    [completed lock];
    __weak typeof (self) weakSelf = self;

    TSNetworkSuccessBlock successBlock = ^(NSObject *resultObject, NSMutableURLRequest *request, NSURLResponse *response) {
        XCTAssertNotNil(resultObject, @"nil result obj");
        [weakSelf signalFinished:completed]; //this
    };

    TSNetworkErrorBlock errorBlock = ^(NSObject *resultObject, NSError *error, NSMutableURLRequest *request, NSURLResponse *response) {
        XCTAssertNotNil(error, @"nil error obj");
        [weakSelf signalFinished:completed]; //or this will end the test
    };

    [[TSNetworking sharedSession] setBaseURLString:kNoAuthNeeded];
    [[TSNetworking sharedSession] performDataTaskWithRelativePath:@"something"
                                                       withMethod:HTTP_METHOD_GET
                                                   withParameters:nil
                                             withAddtionalHeaders:nil
                                                      withSuccess:successBlock
                                                        withError:errorBlock];
    [completed waitUntilDate:[NSDate distantFuture]];
    [completed unlock];
}
Tim Sawtell
  • 307
  • 2
  • 7