在 XCTests 中使用 AFHTTPRequestOperation

Using AFHTTPRequestOperation in XCTests

我有以下测试用例代码:

- (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");
}

我原以为测试用例会阻塞并等待 http 请求完成。 奇怪的是 AFHTTPRequestOperation 永远不会到达成功块,即使 url 是有效块。 如果我在 XCTest 之外使用以下代码,则不会发生,将执行成功块。 有人以前看过这个吗?

一些观察:

  1. 您的测试正在冻结,因为 AFNetworking 将其完成块分派到主队列。但是您已经用 dispatch_semaphore_wait 阻塞了主线程,导致了死锁。

    您可以通过将管理器的completionQueue设置为全局队列来解决这个问题,以进行测试,从而消除主线程上信号量引起的死锁:

    - (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. 注意,现在人们会使用 XCTestExpectation 来执行异步测试。这消除了对信号量的需要,巧合的是,也解决了死锁问题:

    - (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. 顺便说一下,无论你使用XCTestExpectation还是semaphore,都要确保成功块和失败块都满足expectation/semaphore.