dispatch_async 的 OCMock 无回调
OCMock for dispatch_async without callback
我的视图控制器上有一个使用 dispatch_async 的方法。一段时间后,它调用另一个方法。在我的测试中,我想验证后续方法是否被调用。
似乎大多数人对处理 OCMock 和 dispatch_async 的建议是使用 XCTestExpectation 并在任务完成时调用 fulfill。但是,在我的测试中,我无法知道任务何时完成,因为该函数没有回调。结果是测试在任务完成之前完成,验证失败。
这里有一个最小的可重现示例来说明我的问题:
视图控制器
@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
正如您在我的测试中看到的那样,如果我在测试中添加睡眠命令,代码可以正常工作。但是,我无法知道延迟会有多长时间,而且我不想将它设置为安全的时间长度,如果实际上它会更短的话。如果有时只需要 0.2 秒,我不想将延迟设置为 5 秒。
有没有办法在 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, 0), ^{
[[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]);
}
我运行这个示例程序以及我的原始生产代码,两者都能完美运行
我的视图控制器上有一个使用 dispatch_async 的方法。一段时间后,它调用另一个方法。在我的测试中,我想验证后续方法是否被调用。
似乎大多数人对处理 OCMock 和 dispatch_async 的建议是使用 XCTestExpectation 并在任务完成时调用 fulfill。但是,在我的测试中,我无法知道任务何时完成,因为该函数没有回调。结果是测试在任务完成之前完成,验证失败。
这里有一个最小的可重现示例来说明我的问题:
视图控制器
@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
正如您在我的测试中看到的那样,如果我在测试中添加睡眠命令,代码可以正常工作。但是,我无法知道延迟会有多长时间,而且我不想将它设置为安全的时间长度,如果实际上它会更短的话。如果有时只需要 0.2 秒,我不想将延迟设置为 5 秒。
有没有办法在 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, 0), ^{
[[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]);
}
我运行这个示例程序以及我的原始生产代码,两者都能完美运行