我正在尝试为一个方法编写一个单元测试,该方法本身创建一个块并将其传递给另一个对象(以便以后可以调用它)。此方法使用 socket.io-objc 向服务器发送请求。它向 socketio 的 sendEvent:withData:andAcknowledge 传递一个回调块,当它接收到来自服务器的响应时将调用该回调块)。这是我要测试的方法:
typedef void(^MyResponseCallback)(NSDictionary * response);
...
-(void) sendCommand:(NSDictionary*)dict withCallback:(MyResponseCallback)callback andTimeoutAfter:(NSTimeInterval)delay
{
__block BOOL didTimeout = FALSE;
void (^timeoutBlock)() = nil;
// create the block to invoke if request times out
if (delay > 0)
{
timeoutBlock = ^
{
// set the flag so if we do get a response we can suppress it
didTimeout = TRUE;
// invoke original callback with no response argument (to indicate timeout)
callback(nil);
};
}
// create a callback/ack to be invoked when we get a response
SocketIOCallback cb = ^(id argsData)
{
// if the callback was invoked after the UI timed out, ignore the response. otherwise, invoke
// original callback
if (!didTimeout)
{
if (timeoutBlock != nil)
{
// cancel the timeout timer
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(onRequestTimeout:) object:timeoutBlock];
}
// invoke original callback
NSDictionary * responseDict = argsData;
callback(responseDict);
}
};
// send the event to the server
[_socketIO sendEvent:@"message" withData:dict andAcknowledge:cb];
if (timeoutBlock != nil)
{
// if a timeout value was specified, set up timeout now
[self performSelector:@selector(onRequestTimeout:) withObject:timeoutBlock afterDelay:delay];
}
}
-(void) onRequestTimeout:(id)arg
{
if (nil != arg)
{
// arg is a block, execute it
void (^callback)() = (void (^)())arg;
callback();
}
}
真正运行时,这一切似乎都运行良好。当我运行单元测试(使用 SenTestingKit 和 OCMock)时,我的问题出现了:
-(void)testSendRequestWithNoTimeout
{
NSDictionary * cmd = [[NSDictionary alloc] initWithObjectsAndKeys:
@"TheCommand", @"msg", nil];
__block BOOL callbackInvoked = FALSE;
__block SocketIOCallback socketIoCallback = nil;
MyResponseCallback requestCallback = ^(NSDictionary * response)
{
STAssertNotNil(response, @"Response dictionary is invalid");
callbackInvoked = TRUE;
};
// expect controller to emit the message
[[[_mockSocket expect] andDo:^(NSInvocation * invocation) {
SocketIOCallback temp;
[invocation getArgument:&temp atIndex:4];
// THIS ISN'T WORKING AS I'D EXPECT
socketIoCallback = [temp copy];
STAssertNotNil(socketIoCallback, @"No callback was passed to socket.io sendEvent method");
}] sendEvent:@"message" withData:cmd andAcknowledge:OCMOCK_ANY];
// send command to dio
[_ioController sendCommand:cmd withCallback:requestCallback];
// make sure callback not invoked yet
STAssertFalse(callbackInvoked, @"Response callback was invoked before receiving a response");
// fake a response coming back
socketIoCallback([[NSDictionary alloc] initWithObjectsAndKeys:@"msg", @"response", nil]);
// make sure the original callback was invoked as a result
STAssertTrue(callbackInvoked, @"Original requester did not get callback when msg recvd");
}
为了模拟响应,我需要捕获(并保留)由我正在测试的方法创建并传递给 sendEvent 的块。通过将“andDo”参数传递给我的期望,我可以访问我正在寻找的块。但是,它似乎没有被保留。因此,当 sendEvent 展开并且我去调用回调时,应该在块中捕获的所有值都显示为 null。结果是当我调用 socketIoCallback 时测试崩溃,它会访问最初作为块的一部分捕获的“回调”值(现在为零)。
我正在使用 ARC,所以我希望“__block SocketIOCallback socketIoCallback”会保留值。我试图将块“复制”到这个变量中,但它似乎仍然没有保留到 sendCommand 的末尾。我能做些什么来强制这个块保留足够长的时间让我模拟响应?
注意:我试过调用 [invocation retainArguments] ,它可以工作,但在测试完成后清理时会在 objc_release 的某个地方崩溃。