-
Mock單例
單例模式是我們在iOS中最常使用的設(shè)計(jì)模式之一。單例模式不需要傳遞任何參數(shù)定铜,它通過一個(gè)類方法返回一個(gè)唯一的實(shí)例伪冰,與我們平常通過實(shí)例化生成一個(gè)個(gè)實(shí)例的場景有所不同。那么我們要stub一個(gè)單例的類的實(shí)例方法話是必須要返回一個(gè)mock對象鹏浅,因?yàn)橹挥衜ock對象才可以做stub操作。那么我們應(yīng)該如何mock單例類呢屏歹,下面有一段宏主要目的是通過category重寫sharedManage讓它返回我們的mock對象隐砸,這樣只要在測試case中初始化一下mock,sharedManage不管在哪里調(diào)用就都會(huì)返回我們需要的mock對象了蝙眶。
好下面我們開始一步一步分析此宏并運(yùn)用到項(xiàng)目中季希。
#define JTKMOCK_SINGLETON(__className,__sharedMethod) \
JTKMOCK_SINGLETON_CATEGORY_DECLARE(__className) \
JTKMOCK_SINGLETON_CATEGORY_IMPLEMENT(__className,__sharedMethod) \
#define JTKMOCK_SINGLETON_CATEGORY_DECLARE(__className) \
\
@interface __className (UnitTest) \
\
+ (instancetype)JTKCreateClassMock; \
\
+ (instancetype)JTKCreatePartialMock:(__className *)obj; \
\
+ (void)JTKReleaseMock; \
\
@end
#define JTKMOCK_SINGLETON_CATEGORY_IMPLEMENT(__className,__sharedMethod) \
\
static __className *mock_singleton_##__className = nil; \
\
@implementation __className (UnitTest) \
\
+ (instancetype)__sharedMethod { \
if (mock_singleton_##__className) return mock_singleton_##__className; \
return invokeSupersequentNoParameters(); \
} \
+ (instancetype)JTKCreateClassMock { \
mock_singleton_##__className = OCMClassMock([__className class]); \
return mock_singleton_##__className; \
} \
\
+ (instancetype)JTKCreatePartialMock:(__className *)obj { \
mock_singleton_##__className = OCMPartialMock(obj); \
return mock_singleton_##__className; \
} \
\
+ (void)JTKReleaseMock { \
mock_singleton_##__className = nil; \
} \
\
@end
好我們來看第一段
#define JTKMOCK_SINGLETON(__className,__sharedMethod) \
JTKMOCK_SINGLETON_CATEGORY_DECLARE(__className) \
JTKMOCK_SINGLETON_CATEGORY_IMPLEMENT(__className,__sharedMethod) \
首先定義了JTKMOCK_SINGLETON宏有倆個(gè)參數(shù), 第一個(gè)__className是類名第二個(gè)參數(shù)是方法名,這里先補(bǔ)充一下宏知識(shí)為什么要加雙下劃線幽纷,因?yàn)檫@樣是為了避免變量名相同而導(dǎo)致問題的可能性式塌,后面的 \ 是什么?它代表行繼續(xù)操作符友浸,當(dāng)定義的宏不能用一行表達(dá)完整時(shí)峰尝,可以用""表示下一行繼續(xù)此宏的定義。還有下邊我們用到的 ## 是一個(gè)符號(hào)連接操作符它是用于連接作用收恢。
JTKMOCK_SINGLETON_CATEGORY_DECLARE 這個(gè)是聲明分類宏
JTKMOCK_SINGLETON_CATEGORY_IMPLEMENT 這個(gè)是實(shí)現(xiàn)宏
接下來我們繼續(xù)往下看
#define JTKMOCK_SINGLETON_CATEGORY_DECLARE(__className) \
\
@interface __className (UnitTest) \
\
+ (instancetype)JTKCreateClassMock; \
\
+ (instancetype)JTKCreatePartialMock:(__className *)obj; \
\
+ (void)JTKReleaseMock; \
\
@end
定義了三個(gè)類方法:
-
JTKCreateClassMock
-
JTKCreatePartialMock
-
JTKReleaseMock
Mock類型分為ClassMock武学,PartialMock
- ClassMock
如果你不想stub很多方法,而且不會(huì)在一個(gè)沒有stub掉的方法被調(diào)用的時(shí)候拋出異撑晌埽可以使用ClassMock劳淆。 - PartialMock
如果沒有stub掉的方法被調(diào)用了,這個(gè)方法會(huì)被轉(zhuǎn)發(fā)到真實(shí)的對象上默赂。這是對mock技術(shù)上的欺騙沛鸵,但是非常有用,當(dāng)有一些類不適合讓自己很好的被stub時(shí)可以用PartialMock。
下面我們看實(shí)現(xiàn)代碼:
首先創(chuàng)建一個(gè)靜態(tài)全局變量在測試中我們可以使用此對象
#define JTKMOCK_SINGLETON_CATEGORY_IMPLEMENT(__className,__sharedMethod) \
static __className *mock_singleton_##__className = nil; \
\
@implementation __className (UnitTest) \
\
+ (instancetype)__sharedMethod { \
if (mock_singleton_##__className) return mock_singleton_##__className; \
return invokeSupersequentNoParameters(); \
}
此時(shí) ## 起到連接變量名作用
例:
JTKMOCK_SINGLETON_CATEGORY_IMPLEMENT(CDUserManager曲掰,sharedManager)
宏展開后:
static CDUserManager *mock_singleton_CDUserManager = nil;
@implementation CDUserManager (UnitTest)
+ (instancetype) sharedManager {
if (mock_singleton_CDUserManager) return mock_singleton_CDUserManager;
return invokeSupersequentNoParameters();
}
我們看一下sharedManager
if (mock_singleton_CDUserManager) return mock_singleton_CDUserManager;
return invokeSupersequentNoParameters();
首先判斷一下是否有mock對象疾捍,如果有則返回mock對象沒有則調(diào)用return
invokeSupersequentNoParameters它是一個(gè)宏,其作用是利用runtime查找原始實(shí)現(xiàn)方法IMP指針栏妖,然后調(diào)用原始單例生成對象乱豆。在這里調(diào)用此宏目的是如果原始的sharedManage的方法有所變動(dòng)此宏也不用改動(dòng)任何代碼,不用維護(hù)相同的倆套單例代碼詳細(xì)原理在Matt大神的文章中可以找到它吊趾。
下面是分別創(chuàng)建ClassMock與PartialMock對象宛裕,具體用法詳見Demo。
+ (instancetype)JTKCreateClassMock { \
mock_singleton_##__className = OCMClassMock([__className class]); \
return mock_singleton_##__className; \
} \
\
+ (instancetype)JTKCreatePartialMock:(__className *)obj { \
mock_singleton_##__className = OCMPartialMock(obj); \
return mock_singleton_##__className; \
} \
\
+ (void)JTKReleaseMock { \
mock_singleton_##__className = nil; \
} \
\
@end
- 如果調(diào)用宏 JTKMOCK_SINGLETON 提示 Too many arguments to function call,expected 0,have 2,請打開你的測試工程的target论泛,找到Build Setting下的Enable Strict Checking of objc_mesSend Calls,設(shè)置為NO
- 好至此宏代碼分析完了下面我們新建個(gè)Demo看看如何使用去測試單例方法揩尸。
#import <XCTest/XCTest.h>
#import "HQYBaseTestCase.h"
#import "HQYSharedManager.h"
#import "ViewController.h"
//用category重寫主類中的方法會(huì)有一個(gè)警告用以下代碼包裝去除
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
JTKMOCK_SINGLETON(HQYSharedManager, sharedManager)
#pragma clang diagnostic pop
@interface DemoMockSingletonTest : HQYBaseTestCase
{
ViewController *_vc;
ViewController *_vcMock;
}
@end
@implementation DemoMockSingletonTest
- (void)setUp {
[super setUp];
_vc = [ViewController new];
_vcMock = OCMPartialMock(_vc);
// [HQYSharedManager JTKCreateClassMock];
// 這里使用PartialMock
[HQYSharedManager JTKCreatePartialMock:[HQYSharedManager sharedManager]];
// 然后使用mock后的對象sutb掉loadCurrentUserName讓其返回我們期望值
OCMStub([mock_singleton_HQYSharedManager loadCurrentUserName]).andReturn(@"stub success");
}
// 清空
- (void)tearDown {
_vc = nil;
_vcMock = nil;
[HQYSharedManager JTKReleaseMock];
[super tearDown];
}
// Stub測試
- (void)testLoadCurrentUserName{
NSString *userName = [[HQYSharedManager sharedManager] loadCurrentUserName];
XCTAssertEqual(userName, @"stub success");
}
// 控制器測試
- (void)testViewControllerLoadUserName{
[_vc settingUserName];
XCTAssertTrue([_vc.currentUserName isEqualToString:@"stub success"],@"沒有成功賦值 錯(cuò)誤顯示:%@",_vc.currentUserName);
}
@end
- 如有理解錯(cuò)誤請立即聯(lián)系修改以免誤導(dǎo)他人謝謝。
- 本文 Demo下載
- iOS單元測試(一)如何Mock-Singleton 單例測試
- iOS單元測試(二)如何優(yōu)雅測試異步網(wǎng)絡(luò)請求