1
votes

I have the following test case code:

- (void)testExample {
    // URL https://api.spotify.com/v1/search?q=album%3AJustified%20artist%3AJustin%20Timberlake&type=album


    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    [[AFHTTPRequestOperationManager manager] GET:@"https://api.spotify.com/v1/search"
                                      parameters:@{@"q":@"album:Justified artist:Justin Timberlake",
                                                   @"type":@"album"}
                                         success:^(AFHTTPRequestOperation *operation, id responseObject) {
                                             dispatch_semaphore_signal(sem);
                                         } failure:^(AFHTTPRequestOperation *operation, NSError *error) {

                                         }
     ];
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

    // This is an example of a functional test case.
    XCTAssert(YES, @"Pass");
}

I was expecting the test case to block and wait for the http request to finish. The strange thing is that the AFHTTPRequestOperation never reaches the success block even the url is a valid one. If I use the following code outside XCTest, it won't happen, the success block will be executed. Has anyone has seen this before?

1

1 Answers

1
votes

A couple of observations:

  1. Your test is freezing because AFNetworking dispatches its completion blocks to the main queue. But you've blocked the main thread with the dispatch_semaphore_wait, resulting in a deadlock.

    You can resolve this by setting the manager's completionQueue to be a global queue for the purposes of the test, thus eliminating the deadlock caused by the semaphore on the main thread:

    - (void)testExample {
        dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    
        AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    
        manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
        [manager GET:@"https://api.spotify.com/v1/search" parameters:@{@"q":@"album:Justified artist:Justin Timberlake", @"type":@"album"} success:^(AFHTTPRequestOperation *operation, id responseObject) {
            XCTAssert(YES, @"Pass"); // you might want more rigorous test of results here
    
            dispatch_semaphore_signal(sem);
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            XCTFail(@"%@", error.localizedDescription);
    
            dispatch_semaphore_signal(sem);
        }];
    
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    }
    
  2. Note, nowadays one would use XCTestExpectation to perform asynchronous tests. This eliminates the need for the semaphore and, coincidentally, resolves the deadlock issue, too:

    - (void)testExample {
        XCTestExpectation *expectation = [self expectationWithDescription:@"asynchronous request"];
    
        AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    
        [manager GET:@"https://api.spotify.com/v1/search" parameters:@{@"q":@"album:Justified artist:Justin Timberlake", @"type":@"album"} success:^(AFHTTPRequestOperation *operation, id responseObject) {
            XCTAssert(YES, @"Pass"); // you might want more rigorous test of results here
    
            [expectation fulfill];
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            XCTFail(@"%@", error.localizedDescription);
    
            [expectation fulfill];
        }];
    
        [self waitForExpectationsWithTimeout:30.0 handler:nil];
    }
    
  3. By the way, whether you use XCTestExpectation or semaphore, make sure that both the success and failure blocks both satisfy the expectation/semaphore.