OCMock を使って AFHTTPClient のサブクラスをテストする

例えば、AFHTTPClient を継承したカスタムクライアントがあるとする。

#import <Foundation/Foundation.h>
#import <AFNetworking/AFHTTPClient.h>

@interface TodoClient : AFHTTPClient

- (void)getTodoList:(void(^)(NSArray*))success
            failure:(void(^)(NSError*))failure;

@end
#import "TodoClient.h"

@implementation TodoClient

- (void)getTodoList:(void (^)(NSArray*))success
            failure:(void (^)(NSError*))failure
{
    [self getPath:@"todo/"
       parameters:nil
          success:^(AFHTTPRequestOperation *operation, id responseObject) {
              NSArray *array = (NSArray*)responseObject;
              NSArray *todos = [array map:^id(id item) {
                  return [Todo todoWithDictionary:(NSDictionary*)item];
              }];
              success(todos);
          }
          failure:^(AFHTTPRequestOperation *operation, NSError *error) {
              failure(error);
          }];
}

@end

カスタムクライアントのユニットテストを書きたい。 でも、呼び出す Web API はまだ完成していない。 そんなときは OCMock の出番。

getPath に期待する値が渡されたかチェックすればいい。 ついでに、引数で渡された Blocks にダミーのデータを渡してしまえ。

#import <SenTestingKit/SenTestingKit.h>
#import "TodoClient.h"

@interface TodoClientTests : SenTestCase

@property (nonatomic) TodoClient *client;
@property (nonatomic) id mock;

@end
#import "TodoClientTests.h"
#import <OCMock/OCMock.h>

@implementation TodoClientTests

- (void)setUp
{
    self.client = [[TodoClient alloc] init];

    // 既存のインスタンスをつかってモックを作成
    self.mock = [OCMockObject partialMockForObject:self.client];
}

- (void)tearDown
{
    self.client = nil;
    self.mock = nil;
}

- (void)testGetTodoList
{
    // getPath の振る舞いを差し替える
    [[[self.mock expect] andDo:^(NSInvocation *invocation) {
        void (^successBlock)(AFHTTPRequestOperation *operation, id responseObject) = nil;

        // atIndex には本来の引数のインデックス(1~) + 1 を指定する
        [invocation getArgument:&successBlock atIndex:4];

        successBlock(nil,@[@{
                     @"name":@"Test",
                     @"description":@"Test Todo"
                     }]);
    }] getPath:@"todo/" parameters:nil success:[OCMArg any] failure:[OCMArg any]];
    
    [self.mock getTodoList:^(NSArray *todos) {
        Todo *todo = [todos objectAtIndex:0];
        STAssertEqualObjects(@"Test", todo.name, @"match");
        STAssertEqualObjects(@"Test Todo", todo.description, @"match");
    } failure:nil];

    [self.mock verify];
}

@end

getArgument:atIndex に指定するインデックスがややこしい。 ここ嵌りポイント。

引数に Blocks を受け取るメソッドのテストは、この方法でほとんど書けた。