如何对我的 NSURLConnection 委托进行单元测试?我创建了一个 ConnectionDelegate 类,它符合不同的协议,将来自网络的数据提供给不同的 ViewController。在我走得太远之前,我想开始编写我的单元测试。但我不知道如何在没有互联网连接的情况下将它们作为一个单元进行测试。我还想如何处理异步回调。
6 回答
这类似于乔恩的回应,但无法将其放入评论中。第一步是确保您没有创建真正的连接。实现这一点的最简单方法是将连接的创建拉入工厂方法,然后在测试中替换工厂方法。使用 OCMock 的部分模拟支持,这可能看起来像这样。
在你的真实课堂上:
- (NSURLConnection *)newAsynchronousRequest:(NSURLRequest *)request
{
return [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
在您的测试中:
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]];
现在,当您的被测对象尝试创建 URL 连接时,它实际上获得了在测试中创建的虚拟连接。虚拟连接不一定是有效的,因为我们没有启动它并且它永远不会被使用。如果您的代码确实使用了连接,您可以返回另一个模拟,一个模拟 NSURLConnection 的模拟。
第二步是调用触发创建 NSURLConnection 的对象上的方法:
[objectUnderTest doRequest];
因为被测对象没有使用真实连接,我们现在可以从测试中调用委托方法。对于我们使用另一个模拟的 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];
就是这样。您已经有效地伪造了被测对象与连接的所有交互,现在您可以检查它是否处于它应该处于的状态。
如果您想查看一些“真实”代码,请查看 CCMenu 项目中使用 NSURLConnections 的类的测试。这有点令人困惑,因为被测试的类也被命名为连接。
编辑(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;
}
在测试方法结束时,确保事情没有超时:
STAssertTrue([self waitForCompletion:5.0], @"Timeout");
基本格式:
- (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");
}
===============
我参加聚会迟到了,但我遇到了一个非常简单的解决方案。(非常感谢http://www.cocoabuilder.com/archive/xcode/247124-asynchronous-unit-testing.html)
.h 文件:
@property (nonatomic) BOOL isDone;
.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
}
isDone
在异步方法正在执行的线程中设置为YES
。
所以在这种情况下,我在第 1 步创建并启动了 NSURLConnection 并将它的委托设置为这个测试类。在
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
我设置self.isDone = YES;
。我们跳出while循环并执行测试。完毕。
我避免在单元测试中建立网络。反而:
- 我在一个方法中隔离了 NSURLConnection。
- 我创建了一个测试子类,覆盖该方法以删除 NSURLConnection 的所有痕迹。
- 我编写了一个测试以确保在我需要时调用相关方法。然后我知道它会在现实生活中触发 NSURLConnection。
然后我专注于更有趣的部分:合成具有各种特征的模拟 NSURLResponse,并将它们传递给 NSURLConnectionDelegate 方法。
我最喜欢的做法是继承 NSURLProtocol 并让它响应所有 http 请求 - 或其他协议。然后您在您的方法中注册测试协议并在您的-setup
方法中取消注册它-tearDown
。然后,您可以让此测试协议将一些众所周知的数据返回给您的代码,以便您可以在单元测试中对其进行验证。
我写了几篇关于这个主题的博客文章。与您的问题最相关的可能是Using NSURLProtocol for Injecting Test Data and Unit Testing Asynchronous Network Access。
您可能还想看看我在之前文章中描述的 ILCannedURLProtocol。源代码可在Github获得。
你可能想给这个机会:
https://github.com/marianoabdala/ZRYAsyncTestCase
这是正在单元测试的 NSURLConnection 的一些示例代码: https ://github.com/marianoabdala/ZRYAsyncTestCase/blob/12a84c7f1af1a861f76c7825aef6d9d6c53fd1ca/SampleProject/SampleProjectTests/SampleProjectTests.m#L33-L56
这就是我所做的:
- 获取 XAMPP 控制面板 http://www.apachefriends.org/en/xampp.html
- 启动apache服务器
- 在您的
~/Sites
文件夹中,放置一个测试文件(无论您想要什么数据,我们都会称之为my file.test
)。 - 使用 URL http://localhost/~username/myfile.test启动代理
- 不使用时停止 apache 服务器。