OCMock使用
一磷蛹、安裝及簡單使用:
使用Cocoapod引入:
pod 'OCMock'
簡單使用:
新建一個單元測試文件
引入OCMock
新建一個Person類,添加連個屬性name、age
新建一個簡單的單元測試方法
Mock出一個人圆兵,sub getAge方法返回值為10归形;新建一個人對象,給他的年齡設置為10左胞,我們設置一個斷言,我們mock出的人的年齡和新建人對象的年齡相等举户,Test Success
二烤宙、OCMock作用及實現(xiàn)原理
由上面我們可以看出,對于一些不容易構(gòu)造或不容易獲取的對象俭嘁,此時你可以創(chuàng)建一個虛擬的對象來完成測試躺枕。
實現(xiàn)思想:根據(jù)要mock的對象的class來創(chuàng)建一個對應的對象,并且設置好該對象的屬性和調(diào)用預定方法后的動作(例如返回一個值供填,調(diào)用代碼塊拐云,發(fā)送消息等等),然后將其記錄到一個數(shù)組中近她,接下來開發(fā)者主動調(diào)用該方法叉瘩,最后做一個verify(驗證),從而判斷該方法是否被調(diào)用粘捎,或者調(diào)用過程中是否拋出異常等房揭。
三、使用說明
1.創(chuàng)建Mock對象
1.1 類Mock
id classMock = OCMClassMock([SomeClass class]);
1.2 協(xié)議Mock
id protocolMock = OCMProtocolMock(@protocol(SomeProtocol));
1.3 嚴格的類和協(xié)議Mock
默認的mock方式是nice(方法調(diào)用的時候返回nil或者是返回正確的方法)
嚴格的模式下,mock的對象在調(diào)用沒有被stub(置換)的方法的時候,會拋出異常.
id classMock = OCMStrictClassMock([SomeClass class]);id protocolMock = OCMStrictProtocolMock(@protocol(SomeProtocol));
1.4 部分Mock
id partialMock = OCMPartialMock(anObject)
這樣創(chuàng)建的對象在調(diào)用方法時:
如果方法被stub,調(diào)用stub后的方法.
如果方法沒有被stub,調(diào)用原來的對象的方法.
partialMock 對象在調(diào)用方法后,可以用于稍后的驗證此方法的調(diào)用情況(被調(diào)用,調(diào)用結(jié)果)
1.5 觀察者Mock
id observerMock = OCMObserverMock();
這樣創(chuàng)建的對象可以用于觀察/通知.
2 置換方法
2.1 置換方法(待置換的方法返回objects)
OCMStub([mock someMethod]).andReturn(anObject);
在mock對象上調(diào)用某個方法的時候,這個方法一定返回一個anObject.(也就是說強制替換了某個方法的返回值為anObject)
2.2 置換方法(待置換的方法返回values)
OCMStub([mock aMethodReturningABoolean]).andReturn(YES);
在mock對象上調(diào)用某個方法的時候,這個方法一定返回values.
注意這里的原始值類型一定要和原來的方法的返回值一致.
2.3 委托到另一個方法(置換委托方法到另外一個方法)
OCMStub([mock someMethod]).andCall(anotherObject, @selector(aDifferentMethod));
置換mock 對象的someMethod ==> anotherObject 的aDifferentMethod.
這樣,當mock對象調(diào)用someMethod方法的時候,實際上的操作就是anotherObject 調(diào)用了aDifferentMethod方法.
2.4 置換一個blcok方法.
OCMStub([mock someMethod]).andDo(^(NSInvocation invocation) { / block that handles the method invocation */ });
在mock對象調(diào)用someMethod的時候,andDo后面的block會調(diào)用.block可以從NSInvocation中得到一些參數(shù),然后使用這個NSInvocation對象來構(gòu)造返回值等等.
2.5 置換方法的參數(shù)
OCMStub([mock someMethodWithReferenceArgument:[OCMArg setTo:anObject]]);
OCMStub([mock someMethodWithReferenceArgument:[OCMArg setToValue:OCMOCK_VALUE((int){aValue})]]);
mock對象在調(diào)用某個帶參數(shù)的方法的時候,這個方法的參數(shù)可以被置換.
setTo用來設置對象參數(shù),setToValue用來設置原始值類型的參數(shù).
2.6 調(diào)用某個方法就拋出異常
OCMStub([mock someMethod]).andThrow(anException);
當mock對象調(diào)用someMethod的時候,就會拋出異常
2.7 調(diào)用某個方法就發(fā)送通知
OCMStub([mock someMethod]).andPost(aNotification);
當mock對象調(diào)用someMethod的時候,就會發(fā)送通知.
2.8 鏈式調(diào)用
OCMStub([mock someMethod]).andPost(aNotification).andReturn(aValue);
所有的actions(比如andReturn,andPost)可以鏈式調(diào)用.上面的例子中,mock對象調(diào)用someMethod方法后,發(fā)送通知,返回aValue
2.9 轉(zhuǎn)發(fā)的原來的對象/類
OCMStub([mock someMethod]).andForwardToRealObject();
使用部分mock的時候,使用類方法的可以轉(zhuǎn)發(fā)到原來的對象/原來的類.
這個功能在鏈式調(diào)用或者是使用expectation的時候很有用.
2.10 什么也不做
OCMStub([mock someMethod]).andDo(nil);
可以給andDo傳入nil參數(shù),而不是原來一個block作為參數(shù).
這個功能在使用部分mock/mock類的時候很有用,可以屏蔽原來的行為.
3 驗證作用
3.1 運行后就驗證
id mock = OCMClassMock([SomeClass class]);
/* run code under test */
OCMVerify([mock someMethod]);
在mock對象調(diào)用someMethod后就開始驗證.(如果這個方法沒有被調(diào)用),就拋出一個錯誤.
在驗證語句中可以使用 參數(shù)約束.
3.2 置換后驗證
id mock = OCMClassMock([SomeClass class]);
OCMStub([mock someMethod]).andReturn(myValue);
/* run code under test */
OCMVerify([mock someMethod]);
在置換某個方法(置換了返回的參數(shù))后,然后可以驗證這個方法是否被調(diào)用.
4 參數(shù)約束
4.1 任意參數(shù)約束
OCMStub([mock someMethodWithAnArgument:[OCMArg any]])
OCMStub([mock someMethodWithPointerArgument:[OCMArg anyPointer]])
OCMStub([mock someMethodWithSelectorArgument:[OCMArg anySelector]])
不管傳遞什么參數(shù),對于所有活躍的invocations,置換該方法.Pointers 和selectors 需要像上面一樣特殊對待.對于既不是對象,也不是指針,更不是SEL類型的,不可以忽略的參數(shù),可以使用 any 來代替.
4.2 忽略非對象的參數(shù)
[[[mock stub] ignoringNonObjectArgs] someMethodWithIntArgument:0]
在這個invocation中,mock忽略所有的非對象參數(shù).mock對象將會接收所有的someMethodWithIntArgument 方法 invocation,而不去管實際傳遞進來的參數(shù)是什么.如果這個方法含有對象參數(shù)和非對象參數(shù),對象參數(shù)仍然可以使用OCMArg的參數(shù)約束.
4.3 匹配參數(shù)
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 */ }]])
如果在置換創(chuàng)建的時候,有個一個參數(shù)傳遞進來了,置換方法將僅僅匹配精確參數(shù)的invocations.帶不同的參數(shù)來調(diào)用的方法不會被匹配.
OCMArg類提供了幾個不同的方法來匹配不同的參數(shù)類型.
對于checkWithSelector:onObject:方法, 當mock對象接收到someMethod:的時候, 會觸發(fā) anObject上的aSelector方法. 如果方法帶參數(shù),這個參數(shù)會傳遞給someMethod:. 這個方法應該返回一個BOOL值,表示這個參數(shù)是否和預期的一樣.
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).這讓置換調(diào)用直接作用在mock對象上,而不是原有的類.
注意:
添加到類方法上的mock對象跨越了多個測試,mock的類對象在置換后不會deallocated,需要手動來取消這個mock關系.
如果mock對象作用于同一個類, 這時的行為就不預測了.
5.2 驗證類方法的調(diào)用
id classMock = OCMClassMock([SomeClass class]);
/* run code under test */
OCMVerify([classMock aClassMethod]);
驗證類方法的調(diào)用和驗證實例方法的調(diào)用的使用方式一樣.
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];
置換類方法后,可以將類恢復到原來的狀態(tài),通過調(diào)用stopMocking來完成.
如果在結(jié)束測試前,需要恢復到原來的狀態(tài)的話,這就很有用了.
在mock對象被釋放的時候,stopMocking會自動調(diào)用.
當類恢復到原來的對象,類對象的meta class會變?yōu)樵瓉淼膍eta class.這會移除所有的方法置換.
在調(diào)用了stopMocking之后,不應該繼續(xù)使用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對象,然后替換用 繼承的類來代替原有的類).
這就是說: 使用真實的對象來調(diào)用,即使是使用self,也會影響 置換方法和預期的結(jié)果.
6.2 驗證方法調(diào)用
id partialMock = OCMPartialMock(anObject);
/* run code under test */
OCMVerify([partialMock someMethod]);
驗證方法的調(diào)用和驗證類方法,驗證協(xié)議的調(diào)用類似.
6.3 恢復對象
id partialMock = OCMPartialMock(anObject);
/* do stuff */
[partialMock stopMocking];
真正的對象可以通過調(diào)用stopMocking方法來恢復到原來的狀態(tài).
這種情況只有在結(jié)束測試之前需要恢復到原來狀態(tài).
部分mock對象會在釋放的時候,會自動調(diào)用 stopMocking方法.
當對象轉(zhuǎn)變?yōu)樵瓉淼臓顟B(tài)后,類會變?yōu)樵瓉淼念?也會移除所有的置換方法.
在調(diào)用了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最原始的方法:
創(chuàng)建mock對象
期望調(diào)用某個方法
測試代碼(預想的是這段測試代碼會調(diào)用上面期望調(diào)用的方法.
驗證mock對象(也就是驗證期望的方法是否被調(diào)用了)
如果預期的方法沒有被調(diào)用,或者調(diào)用的時候,傳遞的參數(shù)不對,那么就好產(chǎn)生錯誤.可以使用上面 參數(shù)約束.
嚴格的mock可以用在類和協(xié)議上.
如果有懷疑的話,可以使用 3 驗證作用
7.2 嚴格的mock 和快速失敗
id classMock = OCMStrictClassMock([SomeClass class]);
[classMock someMethod]; // this will throw an exception
上面mock沒有設置任何期望,直接掉調(diào)用某個方法會拋出異常.
當超出去預期的調(diào)用的時候,會立即測試失敗. 只有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,等預期的操作.如果方法被調(diào)用,會調(diào)用置換 方法,確認方法確實被調(diào)用了.
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 出于活躍狀態(tài)的時候才會被調(diào)用.這時,可以將認證延時一會.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會按照在預期中設置好的順序來判斷.只要調(diào)用的不是按照期望的調(diào)用順序,這個時候就會拋出異常.
8 觀察者mock
8.1 準備工作
id observerMock = OCMObserverMock();
[notificatonCenter addMockObserver:aMock name:SomeNotification object:nil];
[[mock expect] notificationWithName:SomeNotification object:[OCMArg any]];
為觀察者和通知創(chuàng)建一個mock對象.
在通知中心注冊對象
預期會調(diào)用這個通知.
8.2 驗證
OCMVerifyAll(observerMock);
目前觀察者 mock 總是嚴格的mock.當一個不在預期中的通知調(diào)用的時候,就會拋出一個異常.
這就是說,單個的通知實際上不是能被驗證的.所有的通知必須按照預期賴設置.他們會在通過調(diào)用OCMVerifyAll來一起驗證.
9 進階話題
9.1 對于普通的mock,快速失敗
對strict mock 對象,在一個mock對象上調(diào)用沒有被mock方法(沒有被置換)的時候,會拋出一個異常,這時候會發(fā)生 快速失敗.
id mock = OCMClassMock([SomeClass class]);
[[mock reject] someMethod];
這種情況下,mock會接受除了someMethod 的所有方法.觸發(fā)someMethod方法會導致快速失敗.
9.2 在OCMVerifyAll時重新拋出異常
在fail-fast的時候會拋出異常,但是這并不一定會導致測試失敗.
通過調(diào)用OCMVerifyAll重新拋出異成味耍可以導致測試失敗.
這個功能在不在預期中的從notifications引發(fā)的invocations出現(xiàn)的時候使用.
9.3 置換創(chuàng)建對象的方法
id classMock = OCMClassMock([SomeClass class]);
OCMStub([classMock copy])).andReturn(myObject);
可以置換創(chuàng)建對象的 類/實例方法.當被置換的方法以 alloc,new,copy,mutableCopy開頭的方法時,OCMock會自動調(diào)整對象的引用計數(shù).
id classMock = OCMClassMock([SomeClass class]);
OCMStub([classMock new])).andReturn(myObject);
盡管可以置換類的new方法,但是不建議這么做.
沒有辦法置換 init 方法,因為這個方法是被mock對象自己實現(xiàn)的.
9.4 基于實例對象的方法替換
id partialMock = OCMPartialMock(anObject);
OCMStub([partialMock someMethod]).andCall(differentObject, @selector(differentMethod));
用一句話概括起來,Method swizzling 會在運行時替換一個方法的實現(xiàn).
使用 partial mock然后調(diào)用 andCall操作可以實現(xiàn)這個方法替換.
當anObject收到someMethod消息時,anObject的實現(xiàn)沒有觸發(fā),相反的,
differentObject的differentMethod得到調(diào)用.
其他方法并不會收到影響,仍然會調(diào)用原來的的方法的實現(xiàn).
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"
由于現(xiàn)在mock的實現(xiàn),所有的someMethod都會置換所處理.所以,即使這個方法被調(diào)用,這個驗證也會失敗.
可以通過在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
不可能創(chuàng)建一個 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