我读了其他帖子,提出了这个问题的解决方案。但是,他们的解决方案需要将hacky代码添加到我的应用程序中才能进行测试。对我来说,清洁代码比单元测试更重要。
我经常在我的应用程序中使用dispatch_async,我在单元测试时遇到了麻烦。问题是我的测试完成后块执行,因为它在主队列上异步运行。有没有办法等到块执行后再继续测试。
我不想仅仅因为单元测试而将完成传递给块
- (viod)viewDidLoad
{
[super viewDidLoad];
// Test passes on this
[self.serviceClient fetchDataForUserId:self.userId];
// Test fails on this because it's asynchronous
dispatch_async(dispatch_get_main_queue(), ^{
[self.serviceClient fetchDataForUserId:self.userId];
});
}
- (void)testShouldFetchUserDataUsingCorrectId
{
static NSString *userId = @"sdfsdfsdfsdf";
self.viewController.userId = userId;
self.viewController.serviceClient = [[OCMockObject niceMockForClass:[ServiceClient class]];
[[(OCMockObject *)self.viewController.serviceClient expect] fetchDataForUserId:userId];
[self.viewController view];
[(OCMockObject *)self.viewController.serviceClient verify];
}
答案 0 :(得分:39)
短暂运行主循环让它调用异步块:
- (void)testShouldFetchUserDataUsingCorrectId {
static NSString *userId = @"sdfsdfsdfsdf";
self.viewController.userId = userId;
self.viewController.serviceClient = [[OCMockObject niceMockForClass:[ServiceClient class]];
[[(OCMockObject *)self.viewController.serviceClient expect] fetchDataForUserId:userId];
[self.viewController view];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
[(OCMockObject *)self.viewController.serviceClient verify];
}
我认为这可能会在负载很重的系统上失败,或者如果你在主线程上有一堆其他东西(定时器或其他块)。如果是这种情况,则需要更长时间运行运行循环(这会减慢测试用例),或者反复运行它直到满足模拟对象的期望或达到超时(这需要向模拟对象添加方法)询问是否满足了它的期望。)
答案 1 :(得分:10)
将执行包装到dispatch_group
,然后等待组通过dispatch_group_wait()
完成所有已发送的块的执行。
答案 2 :(得分:0)
使用类似的方法签名制作一个dispatch_async
包装器,然后调用真实的dispatch_async
。依赖关系将包装器注入生产类并使用它。
然后创建一个模拟包装器,它记录排队的块并有一个额外的方法来同步运行所有排队的块。也许执行递归"阻塞"如果正在执行的块依次排列更多的块。
在单元测试中,将模拟包装器注入被测系统。然后,即使SUT认为它正在进行异步工作,您也可以使所有事情同步发生。
dispatch_group
听起来也是一个很好的解决方案,但需要你的生产课程才能知道"在它排队的块的末尾ping调度组。