OCMock 3 參考


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最原始的方法:

  1. 創建mock對象
  2. 期望調用某個方法
  3. 測試代碼(預想的是這段測試代碼會調用上面期望調用的方法.
  4. 驗證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]];
  1. 為觀察者和通知創建一個mock對象.
  2. 在通知中心注冊對象
  3. 預期會調用這個通知.

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM