Kiwiテストで非同期メソッドの引数に渡したBlockを実行する部分のスタブ化

具体的な例として、AFNetworking(2.0)でログインAPIを叩くんだけどテスト時に通信を行わないようなサンプルを書いた。

* 外部への通信が不要になる
* 実パスワードをテストに埋め込まなくてよくなる
* NSURLProtocolで直接レスポンスを書き換えるテストより柔軟

## 最終形

```objc:HANAPIClient.m
@interface HANAPIClient()
@property(nonatomic, strong) AFHTTPRequestOperationManager *request;
@end

- (void)login:(NSString *)name password:(NSString *)password completionHandler:(void (^)(NSError *errorOrNil))completionHandler
{
  [self.request POST:@"https://www.example.jp/login"
          parameters:@{@"name": name, @"password": password}
             success:^(AFHTTPRequestOperation *operation, id responseObject) {
                        completionHandler(nil);
             }
             failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                        completionHandler(error);
             }];
}
```

```objc:MyTests.m
#import <Kiwi/Kiwi.h>

#import <AFNetworking/AFNetworking.h>
#import "HANAPIClient.h"

SPEC_BEGIN(HANAPIClientTests)

describe(@"HANAPIClientTests", ^{
  __block HANAPIClient *client;
  beforeEach(^{
    client = [HANAPIClient new];
  });
  it(@"ログインする", ^{
    __block BOOL success = NO;
    id req = [AFHTTPRequestOperationManager mock];
    id block = ^(NSArray* params){
      void (^success)(AFHTTPRequestOperation *operation, id responseObject) = params[2];
      success(nil, nil);
    };
    [req stub:@selector(POST:parameters:success:failure:) withBlock:block];
    [client setValue:req forKey:@"request"];
    [client login:@"-----------hogehoge------------" password:@"hoehogehoge" completionHandler:^(NSError *errorOrNil) {
      success = (errorOrNil == nil);
    }];
    
    [[expectFutureValue(theValue(success)) shouldEventually] beTrue];
  });
});

SPEC_END
```

## 解説

```
it(@"ログインする", ^{
  __block BOOL success = NO;
  [client login:@"-----------hogehoge------------" password:@"hoehogehoge" completionHandler:^(NSError *errorOrNil) {
    success = (errorOrNil == nil);
  }];
  
  [[expectFutureValue(theValue(success)) shouldEventually] beTrue];
});
```

モック化する前の基本的な非同期メソッドのテスト(https://github.com/allending/Kiwi/wiki/Asynchronous-Testing )

```
it(@"ログインする", ^{
  __block BOOL success = NO;

  id req = [AFHTTPRequestOperationManager mock];
  [client setValue:req forKey:@"request"];

  [client login:@"-----------hogehoge------------" password:@"hoehogehoge" completionHandler:^(NSError *errorOrNil) {
    success = (errorOrNil == nil);
  }];
  
  [[expectFutureValue(theValue(success)) shouldEventually] beTrue];
});
```

HANAPIClientのプライベートなプロパティのrequestをモックに置き換える。

テスト時にだけ@interfaceを宣言し直す方がわかりやすい(<a href="http://www.tokoro.me/2012/09/12/objc-private-test/">Objective-Cのテストクラスからプライベートメソッド/プロパティを参照したい</a>)、けどsetValue:forKey のが楽なので使ってる。


```objc:
it(@"ログインする", ^{
  __block BOOL success = NO;

  id req = [AFHTTPRequestOperationManager mock];

  id block = ^(NSArray* params){
    void (^success)(AFHTTPRequestOperation *operation, id responseObject) = params[2];
    success(nil, nil);
  };  
  [req stub:@selector(POST:parameters:success:failure:) withBlock:block];

  [client setValue:req forKey:@"request"];
  
  [client login:@"-----------hogehoge------------" password:@"hoehogehoge" completionHandler:^(NSError *errorOrNil) {
    success = (errorOrNil == nil);
  }];
  
  [[expectFutureValue(theValue(success)) shouldEventually] beTrue];
});
```

内部で利用しているAFHTTPRequestOperationManagerのPOST:parameters:success:failureメソッドをスタブ化する。

このメソッドのふるいまいでスタブ化したいのは返り値じゃなくて、呼び出し時にsuccess/failureのBlockが呼ばれる部分なのでKWMockのstub:withBlockでsuccessを必ず呼び出すようなBlockを渡す。