2013-07-14 9 views
11

を実行すると、テスト対象のメソッドです:OCMockとブロックテスト、ここで

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

私がテストしたいものは、その成功に他の依存関係をブロック/ OCMockObject _credStoreが適切な方法で呼び出されていることです。だから、現在loginCtrlとcredStoreの依存関係はOCMockObjectsであり、それらをスタブ/期待することができます。

loginControllerをスタブして、呼び出したときに何とかそのブロックを実行できますか?私はOCMockでブロックをスタブすることに関するいくつかの質問を見てきました。私は自分のやっていることを頭で囲むことができません。

実際には、コード_credStore saveUserPassが完了し、_credStoreで検証できるように、ブロック([success invoke] ??)を起動するOCMockが必要です。私は停止し

- (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:

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

答えて

13

私は右のあなたに従っている場合、これはあなたがやりたいことがあります。

@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 

このコードは、あなたがそれから確認することができ、指定した引数で呼び出される本当の成功のブロックを強制できます。

+0

実際のコードはコンパイルされません、行にエラーがあります:void(^ successBlock)(NSString * authToken)、基本的に誤った構文です。私の変数のためにそれを修正しました。 –

+2

getArgumentAtIndexメソッドもありませんが、getArgument:atIndex:メソッドがありますが、getArgumentにはどうすればいいですか?私がコンパイルして実行しているが、例外を投げているものを上に更新しました。 –

+0

NSInvocation + OCMAdditions.hがプロジェクトに含まれていることを確認する必要があります(https:// github。com/erikdoe/ocmock/blob/master/Source/OCMock/NSInvocation%2BOCMAdditions.h)これにより、getArgumentAtIndexAsObjectにアクセスできます。最初の2つの引数は 'self'と' cmd'ですので、成功ブロックが必要な場合は3番目の引数が必要です。 –

0

私はあなたがこれを行うことができると思います。ここで私は以下のベンの例に基づいていますが、EXC_BAD_ACCESS例外を取得し、作業していないものですスパイ。このwiki page on spiesを読むと、渡されたブロックをキャプチャしてテストで自分自身で呼び出すことができるようです。

+1

これは便利ですが、この構文を得るためにOCMockからKiwiに切り替えることを意味します。 –

+1

ああ、私は彼がOCMockを使用していたという事実を見逃しているに違いない。 +1キーウィのために: –

7

コード私は多くのブロックをベースにしているので、あなたの質問に精通しています。

ただ、問題を少し言い直すする:

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

あなたがここまで持っているとまったく同じ問題を解決するために、私たちはブロックを呼び出すメソッドと同じシグネチャを持つメソッドを持っているユニットテストのヘルパークラスを作成しましたテストする必要があります。

コードサンプルでは、​​何も返さず、1つのid引数と2つのブロックを取るモックメソッドです。

以下

あなたが必要があると思いモック方法の一例であり、

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

を利用OCMockユニットテストのサンプルは、それが役に立てば幸い!ご不明な点がございましたら教えてください。

関連する問題