OCMock和块testing,执行

以下是testing中的方法:

- (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 }]; } 

我想testing的是,在成功块中的其他依赖/ OCMockObject _credStore被调用适当的方法。 所以目前loginCtrl和credStore的依赖关系是OCMockObjects,我可以对这些存根/期望。

我将存根loginController以某种方式执行该块时调用? 我已经看了一些与OCMock有关的残块的问题,我不能把我的头围绕在他们正在做的事情上,是否适合这种情况。

实际上,我想要做的就是OCMock来启动块([成功调用] ??),以便代码_credStore saveUserPass完成并可以在_credStore上进行validation。

我停下来的地方:

 - (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:下面是Ben的示例,但不工作,得到一个EXC_BAD_ACCESSexception:

 // 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]; } 

如果我跟着你,这可能做你想做的事情:

 @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 

这段代码允许用你指定的参数调用真正的成功块,然后你可以validation它。

我正在使用的代码主要基于块,所以我非常熟悉你的问题。

只是为了重新解释一下问题:

 - (ReturnObject *)methodThatWeWantToTest:(Foobar *)bar { [self.somethingElse doSomethingAndCallBlock:^{ // really want to test this code } secondBock:^{ // even more code to test }]; } 

为了解决你在这里提出的完全相同的问题,我们创build了unit testing助手类,它具有与调用我们需要testing的块的方法相同的签名。

对于你的代码示例,这是一个模拟方法,不返回任何内容,只需要一个id参数和两个块。

下面是您需要的模拟方法的示例,以及使用的OCMockunit testing的示例

 - (void)mockSuccessBlockWithOneArgumentTwoBlocks:(id)firstArgument successBlock:(void (^)(NSString *authtoken))successBlock failureBlock:(void (^)(NSString *errorMessage))failureBlock { successBlock(@"mocked unit test auth token"); } - (void)testLoginWithUserCallsSomethingInCredStoreOnLoginSuccess { LoginService *loginService = [[LoginService alloc] init]; // init mocks we need for this test id credStoreMock = [OCMockObject niceMockForClass:[MyCredStore class]]; id loginCtrlMock = [OCMockObject niceMockForClass:[MyLoginCtrl class]]; // force login controller to call success block when called with loginWithUserPass // onObject:self - if mock method is in the same unit test, use self. if it is helper object, point to the helper object. [[[loginCtrlMock stub] andCall:@selector(mockSuccessBlockWithOneArgumentTwoBlocks:successBlock:failureBlock::) onObject:self] loginWithUserPass:OCMOCK_ANY withSuccess:OCMOCK_ANY failure:OCMOCK_ANY]; // setup mocks loginService.credStore = credStoreMock; loginService.loginCtrl = loginCtrlMock; // expect/run/verify [[credStore expect] callSomethingFromSuccessBlock]; [loginService loginWithUser:@"testUser" andPass:@"testPass"]; [credStore verify]; } 

希望能帮助到你! 如果您有任何问题,请告诉我们,我们已经做好了工作。

我想你可以用间谍来做这件事。 阅读这个维基页面的间谍看起来像你可以捕获传入的块,并在testing中自己调用它。