问题描述
我的视图控制器上有一个使用dispatch_async的方法。一段时间后,它调用另一个方法。在我的测试中,我想验证是否调用了后续方法。
似乎大多数人对Ocmock和dispatch_async的建议是使用XCTestExpectation并在任务完成时调用满载。但是,在我的测试中,由于该函数没有回调,因此我无法知道任务何时完成。结果是测试在任务完成之前已经完成,并且验证失败。
以下是一个可重现的最小示例,它演示了我的问题:
视图控制器
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)usesAsyncQueue{
dispatch_async(dispatch_get_global_queue(disPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]]; //long running task,e.g. network request
dispatch_async(dispatch_get_main_queue(),^{
[self usedInAsyncQueue];
});
});
}
- (void)usedInAsyncQueue{
// we want to verify that this is called
}
@end
测试
@implementation ViewControllerTest
- (void)testUsesAsyncQueue {
ViewController * testViewController = [[ViewController alloc] init];
id viewControllerMock = OCMPartialMock(testViewController);
Ocmexpect([viewControllerMock usedInAsyncQueue]);
[testViewController usesAsyncQueue];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.1]]; //comment this out and the test fails
OCMVerify([viewControllerMock usedInAsyncQueue]);
}
@end
正如您在测试中看到的那样,如果我在测试中添加了sleep命令,则代码可以正常工作。但是,我无法知道延迟将有多长,而且我也不想将其设置为安全的时间长度(如果实际上会更短)。我不想将延迟设置为5秒,如果有时只需要0.2秒。
有没有一种方法可以在发生usedInAsyncQueue
时捕获呼叫,而不是等待一段时间然后再检查?
解决方法
也许最简单的情况如下-然后期望和实现都在同一条消息中。
@implementation Test
- ( void ) testSomeFunc
{
XCTestExpectation * expectation = [[XCTestExpectation alloc] initWithDescription:@"Test"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^ {
// Some long task
// Now done ...
dispatch_async(dispatch_get_main_queue(),^{
// Note this fulfill happens in the test code
[expectation fulfill];
// This calls the original code,not the test code
[self usedInAsyncQueue];
});
});
// Wait for it ...
[self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:10];
}
@end
或者,类似这样,您可以在要测试的消息内完成。
@interface Test
@property (nonatomic,strong) XCTextExpectation * expectation;
@end
@implementation Test
- ( void ) testSomeFunc
{
// Here you need some reference to the expectation as it is fulfilled inside the func itself later
self.expectation = [[XCTestExpectation alloc] initWithDescription:@"Test"];
// Some code that eventually calls testFunc async and in a block
// Note this calls *test*Func,it calls test code
...
// Wait for it as before
}
// This is a test func
- ( void ) testFunc
{
// Here the fulfill takes place inside test
[self.expectation fulfill];
// rest of func
// here you call the code itself,not the test...
}
@end
编辑4
这里是使用块参数的另一种选择,您的代码或多或少保持原样。
视图控制器
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
// Using a completion block here specifically for testing
// Call this with nil when not testing
- (void)usesAsyncQueue:( void (^)( void ) ) completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,^{
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]]; //long running task,e.g. network request
dispatch_async(dispatch_get_main_queue(),^{
if ( completion )
{
completion();
}
[self usedInAsyncQueue];
});
});
}
测试
@implementation ViewControllerTest
- (void)testUsesAsyncQueue {
ViewController * testViewController = [[ViewController alloc] init];
id viewControllerMock = OCMPartialMock(testViewController);
XCTestExpectation * expectation = [[XCTestExpectation alloc] initWithDescription:@"Test"];
// Here we pass a block that will fulfill the expectation
[testViewController usesAsyncQueue:^ { expectation.fulfill; }];
// Wait for it ...
[self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:10];
}
,
技巧是将要验证的方法存根,并使其调用fulfill
。在OP中的示例中,您将对方法usedInAsyncQueue
进行存根并将其称为fulfill
。然后,您可以等待它被调用,并在超时后失败测试。
这是完整的示例测试
- (void)testUsesAsyncQueue {
ViewController * testViewController = [[ViewController alloc] init];
id viewControllerMock = OCMPartialMock(testViewController);
XCTestExpectation * expectation = [[XCTestExpectation alloc] initWithDescription:@"test"];
OCMStub([viewControllerMock usedInAsyncQueue]).andDo(^(NSInvocation* invocation){
[expectation fulfill];
});
OCMExpect([viewControllerMock usedInAsyncQueue]);
[testViewController usesAsyncQueue];
[self waitForExpectations:@[expectation] timeout:5.1];
OCMVerify([viewControllerMock usedInAsyncQueue]);
}
我运行了该示例程序以及原始的生产代码,两者都运行良好