LoginSignup
56
56

More than 5 years have passed since last update.

iOSでの単体テストをとてもシンプルにできる!XCTestとOHHTTPStubsを使ってAFNetworkingの単体テストをやってみた

Last updated at Posted at 2014-01-08

概要

前回の続きで今回はOHHTTPStubsを使用して単体テストを行ってみました。
(テスト対象となるソースコードの概要は前回のコンテンツを参照してください)

OHHTTPStubsはサーバサイドの処理を用意することなく、クライアントのみでHTTP通信のスタブを用意できるとても便利なライブラリです。

今回はTRVSMonitorというライブラリも一緒に使用しています。
XCTestは非同期処理を待たずにテストケースを終了してしまう場合があるのですが、それを防いでくれるのがTRVSMonitorです。

手順

OHHTTPStubsもCocoaPodsでインストールします。

platform :ios, '7.0'
pod "AFNetworking", "~> 2.0"
pod 'KissXML'
target :BlogProjectTest, :exclusive => true do
  pod 'OHHTTPStubs'
  pod 'TRVSMonitor'
end

テスト開始時のテストデータを用意する目的でOHHTTPStubsを使用します。
今回は使いやすいようにOHHTTPStubsのユーティリティクラスを用意しました。
必要となるのはレスポンスの結果、ステータスコード、コンテントタイプ、最後にエンドポイントです。
これらを自由にカスタマイズできるのでHTTP通信のほとんどをテストできます。
また今回は使用しませんが、リクエストタイム、レスポンスタイムも調整できます。

@interface ASHTTPStubUtil : NSObject

+ (void)setupResponseWithFile:(NSString *)fileName statusCode:(int)statusCode
         contentTypes:(NSDictionary *)contentTypes endPoint:(NSString *)endPoint;

+ (void)setupResponseWithData:(NSData *)data statusCode:(int)statusCode
                 contentTypes:(NSDictionary *)contentTypes endPoint:(NSString *)endPoint;

+ (void)setupResponseXML:(NSString *)fileName statusCode:(int)statusCode
                endPoint:(NSString *)endPoint;

+ (void)removeAll;

@end

@implementation ASHTTPStubUtil

+ (void)setupResponseXML:(NSString *)fileName statusCode:(int)statusCode endPoint:(NSString *)endPoint
{
    [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
        return [request.URL.absoluteString isEqualToString:endPoint];
    } withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) {
        return [OHHTTPStubsResponse responseWithFileAtPath:OHPathForFileInBundle(fileName, nil)
                                                statusCode:statusCode headers:@{@"Content-Type":@"application/rss+xml"}];
    }];
}

+ (void)setupResponseWithFile:(NSString *)fileName statusCode:(int)statusCode
             contentTypes:(NSDictionary *)contentTypes endPoint:(NSString *)endPoint
{
    [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
        return [request.URL.absoluteString isEqualToString:endPoint];
    } withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) {
        return [OHHTTPStubsResponse responseWithFileAtPath:OHPathForFileInBundle(fileName, nil)
                                                statusCode:statusCode headers:contentTypes];
    }];
}

+ (void)setupResponseWithData:(NSData *)data statusCode:(int)statusCode
             contentTypes:(NSDictionary *)contentTypes endPoint:(NSString *)endPoint
{
    [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
        return [request.URL.absoluteString isEqualToString:endPoint];
    } withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) {
        return [OHHTTPStubsResponse responseWithData:data statusCode:statusCode headers:contentTypes];
    }];
}

+ (void)removeAll
{
    [OHHTTPStubs removeAllStubs];
}

@end

次は実際のテストコードです。
詳細な説明は省きますが、コメントを見てもらえば何をしているかは理解できると思います。
ポイントとしてはTRVSMonitorを使ってブロック内の処理が完了するまで待機し、処理が完了したら処理を再開してテストを実行しています。

@implementation ASBlogManagerTests

- (void)setUp
{
    [super setUp];
    // Put setup code here; it will be run once, before the first test case.
}

- (void)tearDown
{
    // Put teardown code here; it will be run once, after the last test case.
    [super tearDown];
}

- (void)testFetchBlogsWithCompletionSuccess
{
    // テストデータの設定
    [ASHTTPStubUtil setupResponseXML:@"BlogList.txt"
                          statusCode:200 endPoint:@"http://blog.hatena.ne.jp/-/hotentry/rss"];

    ASBlogManager *manager = [ASBlogManager sharedManager];

    // Monitorの生成
    TRVSMonitor *monitor = [TRVSMonitor monitor];

    __block NSArray *testResult = nil;

    [manager fetchBlogsWithCompletion:^(NSArray *results, NSError *error) {

        testResult = results;

        // ブロックの処理が完了したらシグナルを送る
        [monitor signal];
    }];

    // シグナルを受け取るまで待機する
    [monitor wait];

    // テスト実行
    XCTAssertTrue(testResult.count == 2);

    ASBlog *blog1 = testResult[0];
    XCTAssertTrue([blog1.title isEqualToString:@"タイトル1"]);
    XCTAssertTrue([blog1.body isEqualToString:@"本文test1"]);

    ASBlog *blog2 = testResult[1];
    XCTAssertTrue([blog2.title isEqualToString:@"タイトル2"]);
    XCTAssertTrue([blog2.body isEqualToString:@"本文test2"]);

    // テストデータの廃棄
    [ASHTTPStubUtil removeAll];
}

- (void)testFetchBlogsWithCompletionFailNotFound
{
    // テストデータの設定(ステータスコードを404にする)
    [ASHTTPStubUtil setupResponseXML:@"BlogList.txt"
                          statusCode:404 endPoint:@"http://blog.hatena.ne.jp/-/hotentry/rss"];

    ASBlogManager *manager = [ASBlogManager sharedManager];

    // Monitorの生成
    TRVSMonitor *monitor = [TRVSMonitor monitor];

    __block NSArray *testResult = nil;
    __block NSError *testError = nil;

    [manager fetchBlogsWithCompletion:^(NSArray *results, NSError *error) {

        testResult = results;
        testError = error;

        // ブロックの処理が完了したらシグナルを送る
        [monitor signal];
    }];

    // シグナルを受け取るまで待機する
    [monitor wait];

    // テスト実行
    // エラーのためデータが返ってこないことを期待する
    XCTAssertNil(testResult);
    // エラー情報が含まれることを期待する
    XCTAssertNotNil(testError);

    // テストデータの廃棄
    [ASHTTPStubUtil removeAll];
}

@end

概要

テストデータの切替が容易なので、前回使用したOCMockよりもかなりシンプルなテストコードになりました。
HTTP通信周りの単体テストはOHHTTPStubsを利用することでかなり楽に行える事が分かりました。
XCTestで単体テストを行う際に少しでも参考になれば幸いです。

56
56
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
56
56