[iOS] UnitTest で使えるいろんなモックの作りかた

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

今回は iOS アプリの UnitTest でよく使うモックの作りかたを紹介したいと思います。
前提条件としてテスト用ライブラリの GHUnit と OCMock を使っていますので、以下で紹介するモックを作りたい場合は導入してください。導入方法はこちらです!

いろんなモックの作りかた

クラスのモックを作る

普通にクラスのモックを作る場合です。まずは以下のようなクラスがあったとします。

CMSample.h

#import <Foundation/Foundation.h>

@interface CMSample : NSObject

- (NSString *)sampleMethod;

@end

CMSample.m

#import "CMSample.h"

@implementation CMSample

#pragma mark - public methods

- (NSString *)sampleMethod
{
    return @"hello";
}

このクラスのモックを作ると以下のようになります。

CMSampleTest.m

#import "CMSampleTest.h"

@implementation CMSampleTest

- (void)test_sampleMethod
{
    // モックの作成
    id mock = [OCMockObject mockForClass:[CMSample class]];
    [[[mock stub] andReturn:@"hoge"] sampleMethod];
    
    // sampleMethod を呼ぶと "hoge" が返ってくるはず
    GHAssertEqualStrings(@"hoge", [mock sampleMethod], @"match");
}

@end

このように OCMockObject#mockForClass を使うと好きなクラスをモックにすることができます!

Delegateのモックを作る

Delegate のモックを作ることもできます。以下のような Delegate をモックにしてみます。didReceive というメソッドだけ定義してあるとってもシンプルなやつです。

CMSampleDelegate.h

#import <Foundation/Foundation.h>

@protocol CMSampleDelegate <NSObject>

- (void)didReceive;

@end

これを呼び出す以下のようなメソッドをテストするとします。

- (void)receive
{
    [self.delegate didReceive];
}

Delegate のモックを作る場合は OCMockObject#mockForProtocol を使います。これで指定の Delegate を実装したモックになります。あとはクラスのオブジェクトが持ってる Delegate をモックに差し替えて終わりです!

- (void)test_receive
{
    // CMSampleDelegate のモックを作る
    id mock = [OCMockObject mockForProtocol:@protocol(CMSampleDelegate)];
    // didReceive が呼ばれることを期待する
    [[mock expect] didReceive];
    
    // CMSample インスタンスの生成
    CMSample *sample = [[CMSample alloc] init];
    // Delegate を差し替える
    sample.delegate = mock;
    
    // テスト対象のメソッドを実行
    [sample receive];
}

クラスメソッドを差し替える

OCMock ではクラスメソッドのスタブを作ることができません。なので method_exchangeImplementations を使ってクラスメソッドを差し替えます。
以下では NSString#stringWithUTF8String を mockMethod に差し替えてみました。

#import <objc/runtime.h>
- (void)testStringWithUTF8String
{
    // 差し替える対象のメソッド
    Method original = class_getClassMethod([NSString class], @selector(stringWithUTF8String:));
    // 差し替えるメソッド
    Method mock = class_getInstanceMethod([self class], @selector(mockMethod));
    
    // メソッドの差し替え
    method_exchangeImplementations(original, mock);
    
    // テスト対象メソッドの実行
    NSString *result = [NSString stringWithUTF8String:"test"];
    GHAssertEqualStrings(@"hoge", result, @"match");
    
    // メソッドを元に戻す
    method_exchangeImplementations(mock, original);
}

- (NSString *)mockMethod
{
    return @"hoge";
}

同じクラスメソッドはすべて差し替わっちゃうので注意が必要です。

まとめ

以上、すごーく簡単にまとめました。
基本的にはいろいろな方法でモックを作ることができますが、モックを作るのが難しい、または無理な場合もあるので注意しましょう。
ということで、単体テストを良い感じに書きたいと思ってる人はぜひ参考にしていただければと思います!

参考