編集
OCMock の最新バージョンを詳しく調べたところ、直接テストしているクラス メソッドをモックする方法を解釈するのは正しいと思います。問題は単に間違ったシグネチャで呼び出しているということですか?
以下の回答は一般的なケースです (テスト対象のメソッドがクラス メソッドを呼び出す場合にも役立ちます)。
オリジナル
OCMock でクラス メソッドをチェックするのは少し難しいです。あなたが現在行っていることは、detailMock というモックオブジェクトを作成し、 getBoolVal というインスタンスメソッドをスタブ化することです: Apple のガイドラインに従いたい場合は、ゲッターで「get」という単語を使用しないことをお勧めします (get set へのポインター参照を送信している場合を除きます)。detailMock は ID であり、任意のセレクターに応答する意思があるため、コンパイルは失敗しません。
では、Class メソッドをテストするにはどうすればよいでしょうか。一般的なケースでは、スウィズリングを行う必要があります。これが私のやり方です。
NSURLConnection
クラスにも適用できるように偽装する方法を見てみましょう。
クラスを拡張することから始めます。
@interface FakeNSURLConnection : NSURLConnection
+ (id)sharedInstance;
+ (void)setSharedInstance:(id)sharedInstance;
+ (void)enableMock:(id)mock;
+ (void)disableMock;
- (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate;
@end
connectionWithRequest:delegate のテストに関心があり、クラスを拡張して、クラスメソッドと同じシグネチャを持つパブリックインスタンスメソッドを追加したことに注意してください。実装を見てみましょう。
@implementation FakeNSURLConnection
SHARED_INSTANCE_IMPL(FakeNSURLConnection);
SWAP_METHODS_IMPL(NSURLConnection, FakeNSURLConnection);
DISABLE_MOCK_IMPL(FakeNSURLConnection);
ENABLE_MOCK_IMPL(FakeNSURLConnection);
+ (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate {
return [FakeNSURLConnection.sharedInstance connectionWithRequest:request delegate:delegate];
}
- (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate { return nil; }
@end
それで、ここで何が起こっているのですか?最初に、以下で説明するいくつかのマクロがあります。次に、クラス メソッドをオーバーライドして、インスタンス メソッドを呼び出すようにしました。を使用OCMock
してインスタンス メソッドをモックできるので、クラス メソッドにインスタンス メソッドを呼び出させることで、クラス メソッドにモックを呼び出させることができます。
ただし、実際のコードでは FakeNSURLConnection を使用したくありませんが、テストでは使用したいと考えています。どうすればこれを行うことができますか?と の間でクラス メソッドを入れ替えることができます。つまり、 callで呼び出しをスウィズルした後です。これにより、マクロが表示されます。NSURLConnection
FakeNSURLConnection
NSURLConnection connectionWithRequest:delegate
FakeNSURLConnection connectionWithRequest:delegate
#define SWAP_METHODS_IMPL(REAL, FAKE) \
+ (void)swapMethods \
{ \
Method original, mock; \
unsigned int count; \
Method *methodList = class_copyMethodList(object_getClass(REAL.class), &count); \
for (int i = 0; i < count; i++) \
{ \
original = class_getClassMethod(REAL.class, method_getName(methodList[i])); \
mock = class_getClassMethod(FAKE.class, method_getName(methodList[i])); \
method_exchangeImplementations(original, mock); \
} \
free(methodList); \
}
#define DISABLE_MOCK_IMPL(FAKE) \
+ (void)disableMock \
{ \
if (_mockEnabled) \
{ \
[FAKE swapMethods]; \
_mockEnabled = NO; \
} \
}
#define ENABLE_MOCK_IMPL(FAKE) \
static BOOL _mockEnabled = NO; \
+ (void)enableMock:(id)mockObject; \
{ \
if (!_mockEnabled) \
{ \
[FAKE setSharedInstance:mockObject]; \
[FAKE swapMethods]; \
_mockEnabled = YES; \
} \
else \
{ \
[FAKE disableMock]; \
[FAKE enableMock:mockObject]; \
} \
}
#define SHARED_INSTANCE_IMPL() \
+ (id)sharedInstance \
{ \
return _sharedInstance; \
}
#define SET_SHARED_INSTANCE_IMPL() \
+ (void)setSharedInstance:(id)sharedInstance \
{ \
_sharedInstance = sharedInstance; \
}
クラスメソッドを誤って再スウィズルしないように、このようなことをお勧めします。では、これをどのように使用しますか?
id urlConnectionMock = [OCMockObject niceMockForClass:FakeNSURLConnection.class];
[FakeNSURLConnection enableMock:urlConnectionMock];
[_mocksToDisable addObject:FakeNSURLConnection.class];
[[[urlConnectionMock expect] andReturn:urlConnectionMock] connectionWithRequest:OCMOCK_ANY delegate:OCMOCK_ANY];
これで終わりです。偽のクラスが呼び出され、モックが呼び出されるように、メソッドを入れ替えました。
あ、でも最後にもう一つ。_mocksToDisable は、スウィズルNSMutableArray
したすべてのクラスのクラス オブジェクトを含む です。
- (void)tearDown
{
for (id mockToDisable in _mocksToDisable)
{
[mockToDisable disableMock];
}
}
これはtearDown
、テストの実行後にクラスのスウィズルを解除したことを確認するために行います。例外が発生した場合、すべてのテスト コードが実行されるわけではありませんが、tearDown は常に実行されるため、テストで正しく実行しないでください。
これを簡単にするモック テクノロジは他にもあるかもしれませんが、1 回記述すれば何度も使用できるため、それほど悪くはないことがわかりました。