逆引きOCMock3
Objective-c / SwiftのMockライブラリの1つであるOCMockの使い方を用途別で主要なポイントのみに絞って纏める.
足りないものについては公式ドキュメントを参照して欲しい.
基本
クラスメソッドをmockする
// mock
id mock = OCMClassMock([SomeClass class]);
OCMStub([mock someMethod]).andReturn(0);
// 呼び出し
[[SomeClass class] someMethod];
インスタンスメソッドをmockする
// mock
id mock = OCMPartialMock([[SomeClass alloc] init]);
OCMStub([mock someMethod]).andReturn(0);
// 呼び出し
// OCMPartialMockの返り値でもinitの際の返り値のどちらを使ってもOK
[mock someMethod];
mockを終了する
[mock stopMocking];
制限
OCMockには以下の制限がある.
- (恐らく) あるクラスの全てのインスタンスについてmockすることはできない (インスタンス毎にmockが必要)
- プリミティブ型の引数に関しては完全マッチしかできない
- Cの関数についてはmockすることはできない
引数に制限を付ける
Objective-cオブジェクト
// equal
OCMStub([mock someMethodWithArg:@"hoge"]).andReturn(0); // equal
// not equal
OCMStub([mock someMethodWithArg:[OCMArg isNotEqual:@"hoge"]).andReturn(0);
// all
OCMStub([mock someMethodWithArg:OCMOCK_ANY]).andReturn(0);
// より細かい判定
OCMStub([mock someMethodWithArg:[OCMArg checkWithBlock:^BOOL(id arg) {
return YES; // 引数が正しければYESを返す
}]]).andReturn(0);
プリミティブ型
完全マッチ以外を扱うことはできない.
// equal
OCMStub([mock someMethodWithInt:1]).andReturn(0);
メソッドの挙動を変える
引数のみ差し替える
NSString* path = @"....";
id mock = OCMPartialMock(instance);
OCMStub([mock someMethodWithPath:OCMOCK_ANY]).andDo(^(NSInvocation* invocation){
[invocation setArgument:&path atIndex:2];
}).andForwardToRealObject();
Indices 0 and 1 indicate the hidden arguments self and _cmd, respectively
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSInvocation_Class/#//apple_ref/occ/instm/NSInvocation/setArgument:atIndex:
なので, atIndex
は2が第一引数になる.
他のメソッドに差し替える
id mock = OCMPartialMock(instance);
// クラスメソッドの場合はotherInstanceの箇所が[SomeClass class]になる
OCMStub([mock someMethodWithArg:OCMOCK_ANY]).andCall(otherInstance, @selector(otherMethodWithArg:));
nilを返す
__block id returnValue = nil;
id mock = OCMPartialMock(instance);
OCMStub([mock someMethodWithArg:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
[invocation setReturnValue:&returnValue];
});
呼びだされたことの確認
OCMStub
の代わりにOCMExpect
を使うことで, OCMVerify
の呼び出し時に呼び出されたかのチェックを行うことができる.
id mock = OCMPartialMock(instance);
OCMExpected([mock someMethod]).andForwardToRealObject();
[mock someMethod]; // 呼び出し
OCMVerifyAll(mock);
メソッド呼び出しを待つ
id mock = OCMPartialMock(instance);
OCMExpect([mock someMethod]).andForwardToRealObject();
[mock someMethod]; // 呼び出し
OCMVerifyAllWithDelay(mock, 5.f); // 第二引数は最大wait時間[s]
引数をチェックする
mockする時に引数に制限を設け, 呼びだされたかを確認することで実現できる.
id mock = OCMPartialMock(instance);
OCMExpect([mock someMethodWithArg:[OCMArg checkWithBlock:^BOOL(id arg) {
return YES; // 引数が正しければYESを返す
}]]).andForwardToRealObject();
[mock someMethod:Arg]; // 呼び出し
OCMVerifyAll(mock);
同じメソッドについて複数回呼び出しを確認する
一つ一つチェックしていくケース
OCMExpect
は呼び出された回数が1以上であるかを見るだけなので, mockした内容は消えない.
追加でOCMExpect
をすると, 最新から順に最初にマッチしたものが使用されるので, 新たに呼び出しの確認をすることができる.
OCMObserverMock
でも同様のことが可能だが, 使い勝手が悪いのでおすすめしない. 1
id mock = OCMPartialMock(instance);
OCMExpect([mock someMethodWithArg:[OCMArg isEqual:A]]).andForwardToRealObject();
[mock someMethodWithArg:A];
OCMExpect([mock someMethodWithArg:[OCMArg isEqual:A]]).andForwardToRealObject(); // ここを消すと, 2回目の呼び出しはチェックされない
[mock someMethodWithArg:A];
OCMVerifyAll(mock);
呼びだされた回数が知りたいケース
イケてる方法が恐らくないので, 自前でカウントアップする.
__block int counter = 0;
id mock = OCMPartialMock(instance);
// MRCの場合は__blockのみで良い.
__block __weak id weakMock = mock;
OCMStub([mock someMethod]).andDo(^(NSInvocation* invocation) {
@synchronized(weakMock) {
count++;
}
}).andForwardToRealObject();
__block __weak
にしないと参照が消えずdeallocされなくなるので注意が必要. (テストなのであまり気にしないと思うが……)
排他制御の必要がない場合は, @synchronized
ごと不要.
-
OCMObserverMockの場合は指定した回数を超えるとクラッシュする挙動になってしまう. ↩