我有一个单元测试,我需要等待异步任务完成。我正在尝试使用NSConditionLock,因为它似乎是一个非常干净的解决方案,但我无法让它工作。
一些测试代码:
- (void)testSuccess
{
loginLock = [[NSConditionLock alloc] init];
Login login = [[Login alloc] init];
login.delegate = self;
// The login method will make an async call.
// I have setup myself as the delegate.
// I would like to wait to the delegate method to get called
// before my test finishes
[login login];
// try to lock to wait for delegate to get called
[loginLock lockWhenCondition:1];
// At this point I can do some verification
NSLog(@"Done running login test");
}
// delegate method that gets called after login success
- (void) loginSuccess {
NSLog(@"login success");
// Cool the delegate was called this should let the test continue
[loginLock unlockWithCondition:1];
}
我试图在这里遵循解决方案: How to unit test asynchronous APIs?
如果我锁定,我的委托永远不会被调用。如果我取出锁码并放入一个简单的计时器,它就能正常工作。
我是否锁定整个线程并且不让登录代码运行并实际进行异步调用?
我也尝试过将登录调用放在另一个线程上,这样它就不会被锁定。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[login login];
});
我做错了什么?
编辑添加登录代码。为了便于阅读,修剪了代码。基本上只需使用AFNetworking来执行POST。完成后将调用委托方法。 登录发出http请求:
NSString *url = [NSString stringWithFormat:@"%@/%@", [_baseURL absoluteString], @"api/login"];
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (_delegate) {
[_delegate loginSuccess];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (_delegate) {
[_delegate loginFailure];
}
}];
答案 0 :(得分:0)
答案可以在https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/AFHTTPRequestOperation.m中找到。
由于您没有设置隐式创建的completionQueue
的{{1}}属性,因此它会调度您阻止的主队列上的回调。
答案 1 :(得分:0)
不幸的是,给定的SO线程中的许多答案(不是全部)(“如何对异步API进行单元测试?”)都是假的,并且包含微妙的问题。大多数作者不关心线程安全性,访问共享变量时需要内存屏障,以及运行循环如何实际工作。实际上,这会导致代码不可靠和无效。
在您的示例中,可能是罪魁祸首,您的委托方法将在主线程上调度。由于您正在等待主线程上的条件锁定,因此会导致死锁。有一点,最接受的答案表明这个解决方案根本没有提及。
首先,更改您的login
方法,使其具有正确的完成处理程序参数,调用站点可以设置该参数以确定登录过程是否已完成:
typedef void (^void)(completion_t)(id result, NSError* error);
- (void) loginWithCompletion:(completion_t)completion;
您可以按如下方式实施登录方法:
- (void) loginWithCompletion:(completion_t)completion
{
NSString *url = [NSString stringWithFormat:@"%@/%@", [_baseURL absoluteString], @"api/login"];
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (completion) {
completion(responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completion) {
completion(nil, error);
}
}];
[self loginWithCompletion:^(id result, NSError* error){
if (error) {
[_delegate loginFailure:error];
}
else {
// Login succeeded with "result"
[_delegate loginSuccess];
}
}];
现在,您有一个可以测试的实际方法。实际上并不确定你要测试的是什么,但是例如:
-(void) testLoginController {
// setup Network MOCK and/or loginController so that it fails:
...
[loginController loginWithCompletion:^(id result, NSError*error){
XCTAssertNotNil(error, @"");
XCTAssert(...);
<signal completion>
}];
<wait on the run loop until completion>
// Test possible side effects:
XCTAssert(loginController.isLoggedIn == NO, @""):
}
对于任何其他进一步的步骤,这可能有所帮助:
如果您不介意使用第三方框架,则可以实施<signal completion>
和<wait on the run loop until completion>
任务以及此答案中所述的其他内容:Unit testing Parse framework iOS