如何在iOS 7.1中使用NSURLSession对HTTP请求和响应进行单元测试?

简·韦恩

我想知道如何使用NSURLSession来“单元测试” HTTP请求和响应。现在,作为单元测试运行时,不会调用我的完成块代码。但是,当从AppDelegate(didFinishWithLaunchingOptions)内部执行相同的代码时,将调用完成块内的代码。如该线程中所建议,未调用NSURLSessionDataTask dataTaskWithURL完成处理程序,需要使用信号量和/或dispatch_group,以“确保主线程被阻塞,直到网络请求完成为止。”

我的HTTP邮政编码如下所示。

@interface LoginPost : NSObject
- (void) post;
@end

@implementation LoginPost
- (void) post
{
 NSURLSessionConfiguration* conf = [NSURLSessionConfiguration defaultSessionConfiguration];
 NSURLSession* session = [NSURLSession sessionWithConfiguration:conf delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
 NSURL* url = [NSURL URLWithString:@"http://www.example.com/login"];
 NSString* params = @"[email protected]&password=test";
 NSMutableRequest* request = [NSMutableURLRequest requestWithURL:url];
 [request setHTTPMethod:@"POST"];
 [request setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
 NSURLSessionDataTask* task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
  NSLog(@"Response:%@ %@\n", response, error); //code here never gets called in unit tests, break point here never is triggered as well
  //response is actually deserialized to custom object, code omitted
 }];
 [task resume];
 }
@end

测试它的方法如下所示。

- (void) testPost
{
 LoginPost* loginPost = [LoginPost alloc];
 [loginPost post];
 //XCTest continues by operating assertions on deserialized HTTP response
 //code omitted
}

在my中AppDelegate,完成块代码确实可以正常工作,如下所示。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
 //generated code
 LoginPost* loginPost = [LoginPost alloc];
 [loginPost post];
}

在单元测试中运行时,如何获得完成块的任何指针?我是iOS的新手,所以一个清晰的例子确实有帮助。

  • 注意:我意识到我要问的也可能不是严格意义上的“单元”测试,因为我依赖HTTP服务器作为我的测试的一部分(意思是,我要问的更像是集成测试)。
  • 注意:我意识到关于NSURLSession的单元测试还有另一个线程NSURLSession的单元测试,但是我不想模拟响应。

Xcode 6现在可以使用来处理异步测试XCTestExpectation在测试异步过程时,您将建立一个“期望”,该过程将异步完成,在发出异步过程之后,您将等待期望满足固定的时间,当查询结束时,您将异步进行达到期望。

例如:

- (void)testDataTask
{
    XCTestExpectation *expectation = [self expectationWithDescription:@"asynchronous request"];

    NSURL *url = [NSURL URLWithString:@"http://www.apple.com"];
    NSURLSessionTask *task = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        XCTAssertNil(error, @"dataTaskWithURL error %@", error);

        if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
            NSInteger statusCode = [(NSHTTPURLResponse *) response statusCode];
            XCTAssertEqual(statusCode, 200, @"status code was not 200; was %d", statusCode);
        }

        XCTAssert(data, @"data nil");

        // do additional tests on the contents of the `data` object here, if you want

        // when all done, Fulfill the expectation

        [expectation fulfill];
    }];
    [task resume];

    [self waitForExpectationsWithTimeout:10.0 handler:nil];
}

我之前的答案(以下)早于XCTestExpectation,但出于历史目的我将保留它。


因为您的测试正在主队列上运行,并且因为请求正在异步运行,所以您的测试不会捕获完成块中的事件。您必须使用信号量或调度组才能使请求同步。

例如:

- (void)testDataTask
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    NSURL *url = [NSURL URLWithString:@"http://www.apple.com"];
    NSURLSessionTask *task = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        XCTAssertNil(error, @"dataTaskWithURL error %@", error);

        if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
            NSInteger statusCode = [(NSHTTPURLResponse *) response statusCode];
            XCTAssertEqual(statusCode, 200, @"status code was not 200; was %d", statusCode);
        }

        XCTAssert(data, @"data nil");

        // do additional tests on the contents of the `data` object here, if you want

        // when all done, signal the semaphore

        dispatch_semaphore_signal(semaphore);
    }];
    [task resume];

    long rc = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 60.0 * NSEC_PER_SEC));
    XCTAssertEqual(rc, 0, @"network request timed out");
}

信号量将确保在请求完成之前测试不会完成。

显然,我上面的测试只是发出一个随机的HTTP请求,但希望它能说明这个想法。我的各种XCTAssert陈述将确定四种错误类型:

  1. NSError对象不是零。

  2. HTTP状态码不是200。

  3. NSData目标是零。

  4. 完成区块未在60秒内完成。

您可能还会为响应的内容添加测试(在此简化示例中我没有这样做)。

注意,上面的测试有效,因为在我的完成块中没有任何东西可以向主队列发送任何东西。如果您正在使用需要主队列的异步操作进行测试(如果您不小心使用AFNetworking或自己手动分派到主队列,则会发生这种情况),则可能会遇到上述模式的死锁(因为我们正在阻止等待网络请求完成的主线程)。但在的情况下NSURLSession,此模式效果很好。


您询问是否要从命令行进行测试,而与模拟器无关。有两个方面:

  1. 如果要从命令行进行测试,则可以从命令行使用xcodebuild例如,要从命令行在模拟器上进行测试,它将是(在我的示例中,我的方案称为NetworkTest):

    xcodebuild test -scheme NetworkTest -destination'platform = iOS Simulator,name = iPhone Retina(3.5-inch),OS = 7.0'
    

    这将构建方案并在指定的目标上运行它。请注意,有许多关于Xcode 5.1问题的报告,这些命令是通过命令行在模拟器上测试应用程序的xcodebuild(我可以验证此行为,因为我有一台机器可以很好地完成上述操作,但是冻结在另一台机器上)。在Xcode 5.1中针对模拟器的命令行测试似乎并不完全可靠。

  2. 如果您不希望测试在模拟器上运行(并且无论是从命令行还是从Xcode进行测试,都适用),则可以构建MacOS X目标,并为该构建建立关联的方案。例如,我将Mac OS X目标添加到了我的应用程序,然后添加了一个NetworkTestMacOS针对它的方案

    顺便说一句,如果您将Mac OS X方案添加到现有的iOS项目中,则测试可能不会自动添加到方案中,因此您可能必须通过编辑方案,导航到“测试”部分并添加测试来手动进行测试在那里上课。然后,您可以通过选择正确的方案从Xcode运行那些Mac OS X测试,或者也可以从命令行进行操作:

    xcodebuild test -scheme NetworkTestMacOS -destination'platform = OS X,arch = x86_64'
    

    还要注意,如果您已经构建了目标,则可以通过导航到正确的DerivedData文件夹(在我的示例中为~/Library/Developer/Xcode/DerivedData/NetworkTest-xxx/Build/Products/Debug),然后xctest直接从命令行运行来直接运行这些测试

    /Applications/Xcode.app/Contents/Developer/usr/bin/xctest -XCTest所有NetworkTestMacOSTests.xctest
    
  3. 将测试与Xcode会话隔离的另一种方法是在单独的OS X Server上进行测试。请参阅WWDC 2013视频Xcode中的“持续集成和测试”部分要进入此处,这已经超出了原始问题的范围,因此,我只想向您介绍该视频,该视频对该主题进行了很好的介绍。

就个人而言,我真的很喜欢Xcode中的测试集成(它使测试的调试变得无限容易),并且通过使用Mac OS X目标,您可以在此过程中绕过模拟器。但是,如果要从命令行(或OS X Server)执行此操作,则上述方法可能会有所帮助。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何在角度7中对getCurrentNavigation()。extras.state进行单元测试

如何在 angular 7 中使用 jasmine 和 karma 编写单元测试用例

如何在pytest单元测试中使用PropertyMock返回请求响应属性?

如何在iOS7中自上而下实现RGB渐变

如何在iOS 7中从WebApp打开Safari

如何在iOS 7中执行动态/计算属性

如何在iOS 7中为按钮添加约束?

如何在iOS 7中获得ADBannerView的渲染宽度?

iOS 7 TextKit-如何在文本中插入图像?

如何在ImageView iOS7中获取圆形图像

如何在iOS 7下的UIPickerView中更改文本的颜色?

如何在ioS 7设备中启用Camera?

如何在iOS 7中禁用表情符号键盘?

如何在iOS7中绘制透明的UIToolbar或UINavigationBar

如何在iOS 7的UIPickerView中更改文本字体?

如何在iOS7中删除UIWebView的inputAccessoryView?

如何在iOS7中更改UISearchBar的inputView?

如何在iOS7中更改UISegmentedControl边框的颜色?

如何在iOS 7中禁用向右导航栏按钮?

如何在iOS 7中更改UILocalizedIndexedCollation的背景颜色

如何在iOS 7中禁用UIAlertView按钮?

如何在iOS7中横向扩展UIToolbar?

如何在iOS 7中检测水龙头的位置

如何在iOS 7中更改UIWebView键盘toolBar?

[Angular 7+]:如何在另一个服务规范中对服务进行单元测试?

如何在单元测试中使用JSON发送请求

如何在Angular2中使用MockBackend对Http post,put,delete进行单元测试?

如何对angular7中具有@Input变量的组件进行单元测试?

如何在AngularJS中使用$ http.jsonp对功能进行单元测试?