如何对NSURLConnection代表进行单元测试?

Mox*_*oxy 11 unit-testing ocmock nsurlconnection ios nsurlconnectiondelegate

我如何对我的NSURLConnection代表进行单元测试?我创建了一个ConnectionDelegate类,它符合不同的协议,以便将数据从Web提供给不同的ViewControllers.在我走得太远之前,我想开始编写单元测试.但我不知道如何在没有互联网连接的情况下将它们作为一个单元进行测试.我还想对待异步回调我应该怎么做.

Eri*_*urg 15

这与Jon的回应类似,但不能将其纳入评论.第一步是确保您没有创建真正的连接.实现此目的的最简单方法是将连接的创建拉入工厂方法,然后在测试中替换工厂方法.有了OCMock的部分模拟支持,这可能看起来像这样.

在你真正的课堂上:

- (NSURLConnection *)newAsynchronousRequest:(NSURLRequest *)request
{
    return [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
Run Code Online (Sandbox Code Playgroud)

在你的测试中:

id objectUnderTest = /* create your object */
id partialMock = [OCMockObject partialMockForObject:objectUnderTest];
NSURLConnection *dummyUrlConnection = [[NSURLConnection alloc] 
    initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"file:foo"]] 
    delegate:nil startImmediately:NO];
[[[partialMock stub] andReturn:dummyUrlConnection] newAsynchronousRequest:[OCMArg any]];
Run Code Online (Sandbox Code Playgroud)

现在,当您的测试对象尝试创建URL连接时,它实际上会获得在测试中创建的虚拟连接.虚拟连接不必有效,因为我们没有启动它,它永远不会被使用.如果您的代码确实使用了连接,那么您可以返回另一个模拟,一个模拟NSURLConnection.

第二步是在对象上调用触发NSURLConnection创建的方法:

[objectUnderTest doRequest];
Run Code Online (Sandbox Code Playgroud)

因为被测对象没有使用真正的连接,所以我们现在可以从测试中调用委托方法.对于NSURLResponse,我们使用另一个模拟,响应数据是从测试中其他地方定义的字符串创建的:

int statusCode = 200;
id responseMock = [OCMockObject mockForClass:[NSHTTPURLResponse class]];
[[[responseMock stub] andReturnValue:OCMOCK_VALUE(statusCode)] statusCode];
[objectUnderTest connection:dummyUrlConnection didReceiveResponse:responseMock];

NSData *responseData = [RESPONSE_TEXT dataUsingEncoding:NSASCIIStringEncoding];
[objectUnderTest connection:dummyUrlConnection didReceiveData:responseData];

[objectUnderTest connectionDidFinishLoading:dummyUrlConnection];
Run Code Online (Sandbox Code Playgroud)

而已.您已经有效地伪造了被测对象与连接的所有交互,现在您可以检查它是否处于应该处于的状态.

如果您想查看一些"真实"代码,请查看使用NSURLConnections的CCMenu项目中的类的测试.这有点令人困惑,因为测试的类也被称为连接.

http://ccmenu.svn.sourceforge.net/viewvc/ccmenu/trunk/CCMenuTests/Classes/CCMConnectionTest.m?revision=129&view=markup


J.C*_*.C. 9

编辑(2014年2月18日):我只是偶然发现了一篇更优雅的解决方案.

http://www.infinite-loop.dk/blog/2011/04/unittesting-asynchronous-network-access/

基本上,您有以下方法:

- (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs {
    NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];

    do {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
        if([timeoutDate timeIntervalSinceNow] < 0.0)
            break;
    } while (!done);

    return done;
}
Run Code Online (Sandbox Code Playgroud)

在测试方法结束时,您确保事情没有超时:

STAssertTrue([self waitForCompletion:5.0], @"Timeout");
Run Code Online (Sandbox Code Playgroud)

基本格式:

- (void)testAsync
{
    // 1. Call method which executes something asynchronously 
    [obj doAsyncOnSuccess:^(id result) {
        STAssertNotNil(result);
        done = YES;
    }
    onError:^(NSError *error) [
        STFail();
        done = YES;
    }

    // 2. Determine timeout
    STAssertTrue([self waitForCompletion:5.0], @"Timeout");
}    
Run Code Online (Sandbox Code Playgroud)

==============

我迟到了,但我遇到了一个非常简单的解决方案.(非常感谢http://www.cocoabuilder.com/archive/xcode/247124-asynchronous-unit-testing.html)

.h文件:

@property (nonatomic) BOOL isDone;
Run Code Online (Sandbox Code Playgroud)

.m文件:

- (void)testAsynchronousMethod
{
    // 1. call method which executes something asynchronously.

    // 2. let the run loop do its thing and wait until self.isDone == YES
    self.isDone = NO;
    NSDate *untilDate;
    while (!self.isDone)
    {
        untilDate = [NSDate dateWithTimeIntervalSinceNow:1.0]
        [[NSRunLoop currentRunLoop] runUntilDate:untilDate];
        NSLog(@"Polling...");
    }

    // 3. test what you want to test
}
Run Code Online (Sandbox Code Playgroud)

isDoneYES在异步方法正在执行的线程中设置.

所以在这种情况下,我在第1步创建并启动了NSURLConnection,并将其委托给了这个测试类.在

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response

我设置self.isDone = YES;.我们突破while循环并执行测试.完成.


Jon*_*eid 5

我在单元测试中避免联网.代替:

  • 我在一个方法中隔离了NSURLConnection.
  • 我创建了一个测试子类,重写该方法以删除NSURLConnection的所有痕迹.
  • 我写了一个测试,以确保在我想要的时候调用有问题的方法.然后我知道它会在现实生活中触发NSURLConnection.

然后我专注于更有趣的部分:合成具有各种特征的模拟NSURLResponses,并将它们传递给NSURLConnectionDelegate方法.


Cla*_*och 5

我最喜欢这样做的方法是子类NSURLProtocol并让它响应所有http请求 - 或其他协议.然后,在-setup方法中注册测试协议,并在方法中注销它-tearDown.然后,您可以让此测试协议将一些众所周知的数据提供给您的代码,以便您可以在单元测试中对其进行验证.

我写了几篇关于这个主题的博客文章.与您的问题最相关的可能是使用NSURLProtocol注入测试数据单元测试异步网络访问.

您可能还想查看我之前的文章中描述的ILCannedURLProtocol.该来源可在Github上获得.