LoginSignup
2
1

More than 5 years have passed since last update.

逆引きOCMock3

Last updated at Posted at 2016-08-09

逆引き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には以下の制限がある.

引数に制限を付ける

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ごと不要.



  1. OCMObserverMockの場合は指定した回数を超えるとクラッシュする挙動になってしまう. 

2
1
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
2
1