OCMock 3 參考
1.創建Mock對象
1.1 類Mock
id classMock = OCMClassMock([SomeClass class]);
1.2 協議Mock
id protocolMock = OCMProtocolMock(@protocol(SomeProtocol));
1.3 嚴格的類和協議Mock
默認的mock方式是nice(方法調用的時候返回nil或者是返回正確的方法)
嚴格的模式下,mock的對象在調用沒有被stub(置換)的方法的時候,會拋出異常.
id classMock = OCMStrictClassMock([SomeClass class]);id protocolMock = OCMStrictProtocolMock(@protocol(SomeProtocol));
1.4 部分Mock
id partialMock = OCMPartialMock(anObject)
這樣創建的對象在調用方法時:
- 如果方法被stub,調用stub后的方法.
- 如果方法沒有被stub,調用原來的對象的方法.
partialMock 對象在調用方法后,可以用於稍后的驗證此方法的調用情況(被調用,調用結果)
1.5 觀察者Mock
id observerMock = OCMObserverMock();
這樣創建的對象可以用於觀察/通知.
2 置換方法
2.1 置換方法(待置換的方法返回objects)
OCMStub([mock someMethod]).andReturn(anObject);
在mock對象上調用某個方法的時候,這個方法一定返回一個anObject.(也就是說強制替換了某個方法的返回值為anObject)
2.2 置換方法(待置換的方法返回values)
OCMStub([mock aMethodReturningABoolean]).andReturn(YES);
在mock對象上調用某個方法的時候,這個方法一定返回values.
注意這里的原始值類型一定要和原來的方法的返回值一致.
2.3 委托到另一個方法(置換委托方法到另外一個方法)
OCMStub([mock someMethod]).andCall(anotherObject, @selector(aDifferentMethod));
置換mock 對象的someMethod ==> anotherObject 的aDifferentMethod.
這樣,當mock對象調用someMethod方法的時候,實際上的操作就是anotherObject 調用了aDifferentMethod方法.
2.4 置換一個blcok方法.
OCMStub([mock someMethod]).andDo(^(NSInvocation *invocation) { /* block that handles the method invocation */ });
在mock對象調用someMethod的時候,andDo后面的block會調用.block可以從NSInvocation中得到一些參數,然后使用這個NSInvocation對象來構造返回值等等.
2.5 置換方法的參數
OCMStub([mock someMethodWithReferenceArgument:[OCMArg setTo:anObject]]);
OCMStub([mock someMethodWithReferenceArgument:[OCMArg setToValue:OCMOCK_VALUE((int){aValue})]]);
mock對象在調用某個帶參數的方法的時候,這個方法的參數可以被置換.
setTo用來設置對象參數,setToValue用來設置原始值類型的參數.
2.6 調用某個方法就拋出異常
OCMStub([mock someMethod]).andThrow(anException);
當mock對象調用someMethod的時候,就會拋出異常
2.7 調用某個方法就發送通知
OCMStub([mock someMethod]).andPost(aNotification);
當mock對象調用someMethod的時候,就會發送通知.
2.8 鏈式調用
OCMStub([mock someMethod]).andPost(aNotification).andReturn(aValue);
所有的actions(比如andReturn,andPost)可以鏈式調用.上面的例子中,mock對象調用someMethod方法后,發送通知,返回aValue
2.9 轉發的原來的對象/類
OCMStub([mock someMethod]).andForwardToRealObject();
使用部分mock的時候,使用類方法的可以轉發到原來的對象/原來的類.
這個功能在鏈式調用或者是使用expectation的時候很有用.
2.10 什么也不做
OCMStub([mock someMethod]).andDo(nil);
可以給andDo傳入nil參數,而不是原來一個block作為參數.
這個功能在使用部分mock/mock類的時候很有用,可以屏蔽原來的行為.
3 驗證作用
3.1 運行后就驗證
id mock = OCMClassMock([SomeClass class]);
/* run code under test */
OCMVerify([mock someMethod]);
在mock對象調用someMethod后就開始驗證.(如果這個方法沒有被調用),就拋出一個錯誤.
在驗證語句中可以使用 參數約束.
3.2 置換后驗證
id mock = OCMClassMock([SomeClass class]);
OCMStub([mock someMethod]).andReturn(myValue);
/* run code under test */
OCMVerify([mock someMethod]);
在置換某個方法(置換了返回的參數)后,然后可以驗證這個方法是否被調用.
4 參數約束
4.1 任意參數約束
OCMStub([mock someMethodWithAnArgument:[OCMArg any]])
OCMStub([mock someMethodWithPointerArgument:[OCMArg anyPointer]])
OCMStub([mock someMethodWithSelectorArgument:[OCMArg anySelector]])
不管傳遞什么參數,對於所有活躍的invocations,置換該方法.Pointers 和selectors 需要像上面一樣特殊對待.對於既不是對象,也不是指針,更不是SEL類型的,不可以忽略的參數,可以使用 any 來代替.
4.2 忽略非對象的參數
[[[mock stub] ignoringNonObjectArgs] someMethodWithIntArgument:0]
在這個invocation中,mock忽略所有的非對象參數.mock對象將會接收所有的someMethodWithIntArgument 方法 invocation,而不去管實際傳遞進來的參數是什么.如果這個方法含有對象參數和非對象參數,對象參數仍然可以使用OCMArg的參數約束.
4.3 匹配參數
OCMStub([mock someMethod:aValue)
OCMStub([mock someMethod:[OCMArg isNil]])
OCMStub([mock someMethod:[OCMArg isNotNil]])
OCMStub([mock someMethod:[OCMArg isNotEqual:aValue]])
OCMStub([mock someMethod:[OCMArg isKindOfClass:[SomeClass class]]])
OCMStub([mock someMethod:[OCMArg checkWithSelector:aSelector onObject:anObject]])
OCMStub([mock someMethod:[OCMArg checkWithBlock:^BOOL(id value) { /* return YES if value is ok */ }]])
如果在置換創建的時候,有個一個參數傳遞進來了,置換方法將僅僅匹配精確參數的invocations.帶不同的參數來調用的方法不會被匹配.
OCMArg類提供了幾個不同的方法來匹配不同的參數類型.
對於checkWithSelector:onObject:方法, 當mock對象接收到someMethod:的時候, 會觸發 anObject上的aSelector方法. 如果方法帶參數,這個參數會傳遞給someMethod:. 這個方法應該返回一個BOOL值,表示這個參數是否和預期的一樣.
4.4 使用Hamcrest來匹配
OCMStub([mock someMethod:startsWith(@"foo")]
OCMock不帶 Hamcrest 框架,所以如果想要使用的話,需要自己安裝Hamcrest .
5 類方法的Mock
5.1 置換類方法
id classMock = OCMClassMock([SomeClass class]);
OCMStub([classMock aClassMethod]).andReturn(@"Test string");
// result is @"Test string"
NSString *result = [SomeClass aClassMethod];
置換類方法和置換實例方法的步驟相像.但是mock對象在深層次上對原有 類做了些更改.(替換了原有的的類的meta class).這讓置換調用直接作用在mock對象上,而不是原有的類.
注意:
添加到類方法上的mock對象跨越了多個測試,mock的類對象在置換后不會deallocated,需要手動來取消這個mock關系.
如果mock對象作用於同一個類, 這時的行為就不預測了.
5.2 驗證類方法的調用
id classMock = OCMClassMock([SomeClass class]);
/* run code under test */
OCMVerify([classMock aClassMethod]);
驗證類方法的調用和驗證實例方法的調用的使用方式一樣.
5.3 有歧義的類方法和實例方法
id classMock = OCMClassMock([SomeClass class]);
OCMStub(ClassMethod([classMock ambiguousMethod])).andReturn(@"Test string");
// result is @"Test string"
NSString *result = [SomeClass ambiguousMethod];
置換了類方法,但是類有一個和類方法同名的實例方法,置換類方法的時候,必須使用ClassMethod()
5.4 恢復類
id classMock = OCMClassMock([SomeClass class]);
/* do stuff */
[classMock stopMocking];
置換類方法后,可以將類恢復到原來的狀態,通過調用stopMocking來完成.
如果在結束測試前,需要恢復到原來的狀態的話,這就很有用了.
在mock對象被釋放的時候,stopMocking會自動調用.
當類恢復到原來的對象,類對象的meta class會變為原來的meta class.這會移除所有的方法置換.
在調用了stopMocking之后,不應該繼續使用mock對象.
6 部分Mock
6.1 置換方法
id partialMock = OCMPartialMock(anObject);
OCMStub([partialMock someMethod]).andReturn(@"Test string");
// result1 is @"Test string"
NSString *result1 = [partialMock someMethod];
// result2 is @"Test string", too!
NSString *result2 = [anObject someMethod];
部分Mock修改了原有的mock對象的類.(實際上是繼承了待mock對象,然后替換用 繼承的類來代替原有的類).
這就是說: 使用真實的對象來調用,即使是使用self,也會影響 置換方法和預期的結果.
6.2 驗證方法調用
id partialMock = OCMPartialMock(anObject);
/* run code under test */
OCMVerify([partialMock someMethod]);
驗證方法的調用和驗證類方法,驗證協議的調用類似.
6.3 恢復對象
id partialMock = OCMPartialMock(anObject);
/* do stuff */
[partialMock stopMocking];
真正的對象可以通過調用stopMocking方法來恢復到原來的狀態.
這種情況只有在結束測試之前需要恢復到原來狀態.
部分mock對象會在釋放的時候,會自動調用 stopMocking方法.
當對象轉變為原來的狀態后,類會變為原來的類.也會移除所有的置換方法.
在調用了stopMocking之后,最好不要去使用mock對象.
7 嚴格mock和期望
7.1 Expect-run-verify 期望-運行-驗證
id classMock = OCMClassMock([SomeClass class]);
OCMExpect([classMock someMethodWithArgument:[OCMArg isNotNil]]);
/* run code under test, which is assumed to call someMethod */
OCMVerifyAll(classMock)
這是使用mock最原始的方法:
- 創建mock對象
- 期望調用某個方法
- 測試代碼(預想的是這段測試代碼會調用上面期望調用的方法.
- 驗證mock對象(也就是驗證期望的方法是否被調用了)
如果預期的方法沒有被調用,或者調用的時候,傳遞的參數不對,那么就好產生錯誤.可以使用上面 參數約束.
嚴格的mock可以用在類和協議上.
如果有懷疑的話,可以使用 3 驗證作用
7.2 嚴格的mock 和快速失敗
id classMock = OCMStrictClassMock([SomeClass class]);
[classMock someMethod]; // this will throw an exception
上面mock沒有設置任何期望,直接掉調用某個方法會拋出異常.
當超出去預期的調用的時候,會立即測試失敗. 只有strict mock才會快速失敗.
7.3 置換操作和預期
id classMock = OCMStrictClassMock([SomeClass class]);
OCMExpect([classMock someMethod]).andReturn(@"a string for testing");
/* run code under test, which is assumed to call someMethod */
OCMVerifyAll(classMock)
可以使用andReturn,andThrow,等預期的操作.如果方法被調用,會調用置換 方法,確認方法確實被調用了.
7.4 延時驗證
id mock = OCMStrictClassMock([SomeClass class]);
OCMExpect([mock someMethod]);
/* run code under test, which is assumed to call someMethod eventually */
OCMVerifyAllWithDelay(mock, aDelay);
在某種情況下,預期的方法只有在 run loop 出於活躍狀態的時候才會被調用.這時,可以將認證延時一會.aDelay是mock對象會等待的最大時間.通常情況下,在預期達到后就會返回.
7.5 依序驗證
id mock = OCMStrictClassMock([SomeClass class]);
[mock setExpectationOrderMatters:YES];
OCMExpect([mock someMethod]);
OCMExpect([mock anotherMethod]);
// calling anotherMethod before someMethod will cause an exception to be thrown
[mock anotherMethod];
mock會按照在預期中設置好的順序來判斷.只要調用的不是按照期望的調用順序,這個時候就會拋出異常.
8 觀察者mock
8.1 准備工作
id observerMock = OCMObserverMock();
[notificatonCenter addMockObserver:aMock name:SomeNotification object:nil];
[[mock expect] notificationWithName:SomeNotification object:[OCMArg any]];
- 為觀察者和通知創建一個mock對象.
- 在通知中心注冊對象
- 預期會調用這個通知.
8.2 驗證
OCMVerifyAll(observerMock);
目前觀察者 mock 總是嚴格的mock.當一個不在預期中的通知調用的時候,就會拋出一個異常.
這就是說,單個的通知實際上不是能被驗證的.所有的通知必須按照預期賴設置.他們會在通過調用OCMVerifyAll來一起驗證.
9 進階話題
9.1 對於普通的mock,快速失敗
對strict mock 對象,在一個mock對象上調用沒有被mock方法(沒有被置換)的時候,會拋出一個異常,這時候會發生 快速失敗.
id mock = OCMClassMock([SomeClass class]);
[[mock reject] someMethod];
這種情況下,mock會接受除了someMethod 的所有方法.觸發someMethod方法會導致快速失敗.
9.2 在OCMVerifyAll時重新拋出異常
在fail-fast的時候會拋出異常,但是這並不一定會導致測試失敗.
通過調用OCMVerifyAll重新拋出異常可以導致測試失敗.
這個功能在不在預期中的從notifications引發的invocations出現的時候使用.
9.3 置換創建對象的方法
id classMock = OCMClassMock([SomeClass class]);
OCMStub([classMock copy])).andReturn(myObject);
可以置換創建對象的 類/實例方法.當被置換的方法以 alloc,new,copy,mutableCopy開頭的方法時,OCMock會自動調整對象的引用計數.
id classMock = OCMClassMock([SomeClass class]);
OCMStub([classMock new])).andReturn(myObject);
盡管可以置換類的new方法,但是不建議這么做.
沒有辦法置換 init 方法,因為這個方法是被mock對象自己實現的.
9.4 基於實例對象的方法替換
id partialMock = OCMPartialMock(anObject);
OCMStub([partialMock someMethod]).andCall(differentObject, @selector(differentMethod));
用一句話概括起來,Method swizzling 會在運行時替換一個方法的實現.
使用 partial mock然后調用 andCall操作可以實現這個方法替換.
當anObject收到someMethod消息時,anObject的實現沒有觸發,相反的,
differentObject的differentMethod得到調用.
其他方法並不會收到影響,仍然會調用原來的的方法的實現.
10 使用限制
10.1 在一個指定的類上,只能有一個mock對象
// don't do this
id mock1 = OCMClassMock([SomeClass class]);
OCMStub([mock1 aClassMethod]);
id mock2 = OCMClassMock([SomeClass class]);
OCMStub([mock2 anotherClassMethod]);
原因是類的meta class 替換后,不會釋放,mock類仍會存在,甚至可能跨tests.
如果多個相同mock對象管理同一個類,運行時的行為就不可確定.
10.2 在被置換的方法上設置期望,會不起作用
id mock = OCMStrictClassMock([SomeClass class]);
OCMStub([mock someMethod]).andReturn(@"a string");
OCMExpect([mock someMethod]);
/* run code under test */
OCMVerifyAll(mock); // will complain that someMethod has not been called
上面代碼先替換了someMethod,然后強制someMethod返回”a string”
由於現在mock的實現,所有的someMethod都會置換所處理.所以,即使這個方法被調用,這個驗證也會失敗.
可以通過在expect后添加andReturn來避免這個問題. 也可以通過在expect后再次設置一個方法替換.
10.3 Partial mock 不能在某些特定的類使用
id partialMockForString = OCMPartialMock(@"Foo");
// will throw an exception
NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
id partialMockForDate = OCMPartialMock(date);
// will throw on some architectures
不可能創建一個 toll-free bridged的類,例如 NSString,或者是NSDate.
如果你試圖這么去做,那么可能會拋出一個異常.
10.4 某些特定的類不能被置換和驗證
id partialMockForString = OCMPartialMock(anObject);
OCMStub([partialMock class]).andReturn(someOtherClass); // will not work
不能mock某些運行時的方法,例如
- class,
- methodSignatureForSelector:
- forwardInvocation:
10.5 NSString的類方法不能被置換和驗證
id stringMock = OCMClassMock([NSString class]);
// the following will not work
OCMStub([stringMock stringWithContentsOfFile:[OCMArg any] encoding:NSUTF8StringEncoding error:[OCMArg setTo:nil]]);
10.6 NSObject 的方法不能被驗證
id mock = OCMClassMock([NSObject class]);
/* run code under test, which calls awakeAfterUsingCoder: */
OCMVerify([mock awakeAfterUsingCoder:[OCMArg any]]);
// still fails
不可能在NSObject 和它的分類category上使用verify-after-running.
在某些情況下可能置換這個方法,然后驗證.
10.7 apple 的私有方法不能被驗證
UIWindow *window = /* get window somehow */
id mock = OCMPartialMock(window);
/* run code under test, which causes _sendTouchesForEvent: to be invoked */
OCMVerify([mock _sendTouchesForEvent:[OCMArg any]]);
// still fails
含有下划線前綴,后綴,NS,UI開頭的方法等.
10.8 Verify-after-running不能使用延時
只有在 嚴格的mock和期望中,可以使用expect-run-verify