AFNetworking 2.0 和单元测试

Posted

技术标签:

【中文标题】AFNetworking 2.0 和单元测试【英文标题】:AFNetworking 2.0 and Unit Test 【发布时间】:2014-04-29 21:40:09 【问题描述】:

我正在尝试使用 XCTest 在 XCode 5 中对使用 AFNEtworking 的类进行单元测试。我遇到的问题是我的 AFHTTPRequestOperation 的完成块永远不会被执行。我认为这是运行单元测试的 XCode 和 AFNetworking 的调度队列之间的一些脱节。以下测试用例通过但从未到达完成块中的 NSLog 语句(没有日志输出,也没有捕获在这些语句上设置的断点)。相同的代码在单元测试之外工作。有谁知道如何解决这个问题?我使用 Nocilla 模拟实际请求,结果与使用真实服务器重新调整有效响应的结果相同?

已编辑以使测试失败并记录块中设置的变量

- (void)setUp

    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.



    [[LSNocilla sharedInstance] start];

    stubRequest(@"POST", @"http://www.example.com/module/api/ping").
    andReturn(200).
    withHeaders(@@"Content-Type": @"application/json").
    withBody(@"\"success\":true");

    stubRequest(@"GET", @"http://www.example.com/module/api/ping?testkey=testval").
    andReturn(200).
    withHeaders(@@"Content-Type": @"application/json").
    withBody(@"\"success\":true");


- (void)tearDown

    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];

    [[LSNocilla sharedInstance] stop];
    [[LSNocilla sharedInstance] clearStubs];


- (void)testSanity

    AFSecurityPolicy *policy = [[AFSecurityPolicy alloc] init];
    //[policy setAllowInvalidCertificates:YES];

    AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:[NSURL URLWithString:@"http://www.example.com/module/api/ping"]];
    //manager.operationQueue = [NSOperationQueue mainQueue];
    [manager setSecurityPolicy:policy];


    manager.requestSerializer = [AFJSONRequestSerializer serializer];
    manager.responseSerializer = [AFJSONResponseSerializer serializer];


    __block id resObj = nil;
    __block id resError = nil;

    AFHTTPRequestOperation *req = [manager POST:@"http://www.example.com/module/api/ping"
                                    parameters:[NSDictionary dictionaryWithObject:@"testval" forKey:@"testkey"]
                                       success:^(AFHTTPRequestOperation *operation, id responseObject) 

                                           NSLog(@"Response: %@", responseObject);
                                           resObj = responseObject;
                                           return;

                                       
                                       failure:^(AFHTTPRequestOperation *operation, NSError *error) 

                                            NSLog(@"Error: %@", error);
                                           resError = error;
                                           return;
                                       ];
    [req waitUntilFinished];

    NSLog(@"req.status: %d", req.response.statusCode);
    NSLog(@"req.responseObj: %@", req.responseObject);
    XCTAssertTrue(req.isFinished);
    NSLog(@"resObj: %@", resObj);
    NSLog(@"resError: %@", resError);
    XCTAssertEqual([[req.responseObject objectForKey:@"success"] boolValue], YES);

    XCTAssertEqual([[resObj objectForKey:@"success"] boolValue], YES);

控制台输出

测试用例“-[AppSupportTests testSanity]”已启动。 2014-04-29 16:45:07.424 xctest[72183:303] req.status: 200 2014-04-29 16:45:07.424 xctest[72183:303] req.responseObj: 成功 = 1; 2014-04-29 16:45:07.424 xctest[72183:303] resObj: (null) 2014-04-29 16:45:07.425 xctest[72183:303] resError: (null) /Users/jlujan/Code/AppSupport/AppSupportTests/AppSupportTests.m:114: 错误:-[AppSupportTests testSanity] : (([[resObj objectForKey:@"success"] boolValue]) 等于 (__objc_yes)) 失败:(" NO") 不等于 ("YES") 测试用例“-[AppSupportTests testSanity]”失败(0.003 秒)。

【问题讨论】:

一旦后台操作完成,它可能会调用waitUntilFinished,但这将是在调用回调之前。你可以尝试使用semaphores 到wait 直到你有signaled 对于异步测试我推荐Expecta顺便说一句。然后,您可以执行 expect([[req.responseObject objectForKey:@"success"] boolValue]).will.beTruthy(); 之类的操作(尽管我不喜欢 beTruthy 语法!)。您只需确保设置了适当的超时时间[Expecta setAsynchronousTestTimeout:15];//seconds 这只是一个健全性检查,以确保我的实际代码没有导致它。使用这里的信号量示例***.com/questions/20476957/…,它会永远挂起。无论哪种方式,我都不能在我的实际代码中使用信号量方法。 @Rich,感谢您指向 Expecta。从长远来看,这比找出调度队列向南的方向更好的解决方案。 【参考方案1】:

根据 cmets 中的讨论,我们发现 waitUntilFinished is once the background operation is complete,并且它不会等到调用完成块之后。

有一个更好的异步测试框架 - Expecta。 然后代替调用:

XCTAssertTrue(req.isFinished);
XCTAssertEqual([[resObj objectForKey:@"success"] boolValue], YES);

你可以这样做:

expect(req.isFinished).will.beTruthy();
expect([[resObj objectForKey:@"success"] boolValue]).will.beTruthy();

有lots of other matchers,只要确保在+setUp 方法中设置timeout 和+[Expecta setAsynchronousTestTimeout:]

【讨论】:

以上是关于AFNetworking 2.0 和单元测试的主要内容,如果未能解决你的问题,请参考以下文章

AFNetworking 单元测试

AFNetworking 2.0 解析的数据未显示在表格视图中

单元测试基本框架和8个测试方面

单元测试基本框架和8个测试方面

在单元测试中使用 AFNetworking 监控可达性失败

OAuth 2.0 单元测试解决方案