OCMock and block testing, executing
ios, objective-c, objective-c-blocks, ocmock
Solution
If I follow you right, this may do what you want:
@interface ExampleLC : NSObject
- (void)loginWithUserPass:userPassD withSuccess:(void (^)(NSString *authToken))successBlock failure:(void (^)(NSString *errorMessage))failureBlock;
@end
@implementation ExampleLC
- (void)loginWithUserPass:userPassD withSuccess:(void (^)(NSString *authToken))successBlock failure:(void (^)(NSString *errorMessage))failureBlock
{
}
@end
@interface Example : NSObject {
@public
ExampleLC *_loginCntrl;
}
- (void)saveToken:(NSString *)authToken;
- (void)loginWithUser:(NSString *)userName andPass:(NSString *)pass;
@end
@implementation Example
- (void)saveToken:(NSString *)authToken
{
}
- (void)loginWithUser:(NSString *)userName andPass:(NSString *)pass {
NSDictionary *userPassD = @{@"user":userName,
@"pass":pass};
[_loginCntrl loginWithUserPass:userPassD withSuccess:^(NSString *authToken){
// save authToken to credential store
[self saveToken:authToken];
} failure:^(NSString *errorMessage) {
// alert user pass was wrong
}];
}
@end
@interface loginTest : SenTestCase
@end
@implementation loginTest
- (void)testExample
{
Example *exampleOrig = [[Example alloc] init];
id loginCntrl = [OCMockObject mockForClass:[ExampleLC class]];
[[[loginCntrl expect] andDo:^(NSInvocation *invocation) {
void (^successBlock)(NSString *authToken) = [invocation getArgumentAtIndexAsObject:3];
successBlock(@"Dummy");
}] loginWithUserPass:OCMOCK_ANY withSuccess:OCMOCK_ANY failure:OCMOCK_ANY];
exampleOrig->_loginCntrl = loginCntrl;
id example = [OCMockObject partialMockForObject:exampleOrig];
[[example expect] saveToken:@"Dummy"];
[example loginWithUser:@"ABC" andPass:@"DEF"];
[loginCntrl verify];
[example verify];
}
@end
This code allows forces the real success block to be invoked with the argument you specify, which you can then verify.
Problem
Here's the method under test: ``` - (void)loginWithUser:(NSString *)userName andPass:(NSString *)pass { NSDictionary *userPassD = @{@"user":userName, @"pass":pass}; [_loginCntrl loginWithUserPass:userPassD withSuccess:^(NSString *authToken){ // save authToken to credential store } failure:^(NSString *errorMessage) { // alert user pass was wrong }]; } ``` what I want to test is that in that success block the other dependency/OCMockObject _credStore is called with the appropriate methods. So currently the loginCtrl and credStore dependencies are OCMockObjects and I can stub/expect on those. Would I stub loginController to somehow execute that block when called? I've looked at some of the questions on stubbing blocks with OCMock and I can't wrap my head around what they're doing and if it would be suitable for this situation. In reality all I want to do is OCMock to fire the block ([success invoke]??) so that the code _credStore saveUserPass is done and can be verified on _credStore. where I stopped: ``` - (void)test_loginWithuserPass_succeeds_should_call_credStore_setAuthToken { NSDictionary *userPassD = @{@"user":@"mark", @"pass":@"test"}; id successBlock = ^ { // ??? isn't this done in the SUT? }; [[[_loginController stub] andDo:successBlock] loginWithUserPass:userPassD withSuccess:OCMOCK_ANY failure:OCMOCK_ANY]; [[_credentialStore expect] setAuthToken:@"passed back value from block"]; [_docServiceSUT loginWithUser:@"mark" andPass:@"test"]; [_credentialStore verify]; } ``` ETA: here's what I have based on Ben's example below, but not working, getting a EXC_BAD_ACCESS exception: ``` // OCUnit test method - (void)test_loginWithUserPass_success_block_should_call_credentials_setAuthToken { void (^proxyBlock)(NSInvocation*) = ^(NSInvocation *invocation) { void(^successBlock)(NSString *authToken); [invocation getArgument:&successBlock atIndex:3]; // should be 3 because my block is the second param successBlock(@"myAuthToken"); }; [[[_loginController expect] andDo:proxyBlock] loginWithUserPass:OCMOCK_ANY withSuccess:OCMOCK_ANY failure:OCMOCK_ANY]; [[_credentialStore expect] setAuthToken:@"myAuthToken"]; [_docServiceSUT loginWithUser:@"mark" andPass:@"myPass"]; [_loginController verify]; [_credentialStore verify]; } //method under test - (void)loginWithUser:(NSString *)userName andPass:(NSString *)pass { NSDictionary *userPassD = @{@"user":userName, @"pass":pass}; void(^onSuccess)(NSString *) = ^(NSString *authToken){ [SVProgressHUD dismiss]; [_credentials setAuthToken:authToken]; // Ask user to enter the 6 digit authenticator key [self askUserForAuthenticatorKey]; }; void(^onFailure)(NSString *) = ^(NSString *errorMessage) { [SVProgressHUD dismiss]; [_alertSender sendAlertWithMessage:errorMessage andTitle:@"Login failed"]; }; [SVProgressHUD show]; [_loginCntrl loginWithUserPass:userPassD withSuccess:onSuccess failure:onFailure]; } ```