我正在尝试检查是否使用OCMock调用了类方法。我从OCMock网站和其他答案中搜集了新的OCMock版本(2.1)增加了对存根类方法的支持。
我正在尝试做同样的事情:
DetailViewController:
+(BOOL)getBoolVal
{
return YES;
}
测试用例:
-(void) testClassMethod
{
id detailMock = [OCMockObject mockForClass:[DetailViewController class]];
[[[detailMock stub] andReturnValue:OCMOCK_VALUE((BOOL){YES})] getBoolVal:nil];
}
测试正在运行并且也在成功,但即使我在getBoolVal
中从DetailViewController
方法返回NO而不是YES,它也会成功。在该方法上保留断点时,测试执行不会停止,表明未调用该方法。
如何查看课程方法?
答案 0 :(得分:0)
修改强>
只是仔细查看最新版本的OCMock,我认为你在解释嘲笑你直接测试的类方法的方法时是正确的,所以问题就在于你'用错误的签名来称呼它?
以下答案是一般情况(当测试中的方法调用类方法时也很有用。)
<强>原始强>
使用OCMock检查类方法有点棘手。 你目前正在做的是创建一个名为detailMock的模拟对象并存根一个名为getBoolVal的实例方法:(顺便提一下,你的方法原型没有& #39; t参与进来,所以你不应该把零传递给它 - 为了得到真正的挑剔,如果你想遵循Apple的指导方针,他们建议不要使用&#34这个词。 ;获取&#34;在getter中(除非你发送一个指针引用来获取set))。编译不会失败,因为detailMock是一个id并愿意响应任何选择器。
那么如何测试Class方法呢?对于一般情况,您需要做一些调配。我就是这样做的。
让我们看看我们如何假冒NSURLConnection
你应该如何应用于你的班级。
首先扩展您的课程:
@interface FakeNSURLConnection : NSURLConnection
+ (id)sharedInstance;
+ (void)setSharedInstance:(id)sharedInstance;
+ (void)enableMock:(id)mock;
+ (void)disableMock;
- (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate;
@end
请注意,我对测试connectionWithRequest:delegate感兴趣,并且我已经扩展了类,以添加一个与类方法具有相同签名的公共实例方法。让我们来看看实现:
@implementation FakeNSURLConnection
SHARED_INSTANCE_IMPL(FakeNSURLConnection);
SWAP_METHODS_IMPL(NSURLConnection, FakeNSURLConnection);
DISABLE_MOCK_IMPL(FakeNSURLConnection);
ENABLE_MOCK_IMPL(FakeNSURLConnection);
+ (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate {
return [FakeNSURLConnection.sharedInstance connectionWithRequest:request delegate:delegate];
}
- (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate { return nil; }
@end
那么这里发生了什么?首先,我将在下面讨论一些宏。接下来,我重写了类方法,让它调用实例方法。我们可以使用OCMock
来模拟实例方法,因此通过让类方法调用实例方法,我们可以让类方法调用mock。
我们不想在我们的真实代码中使用FakeNSURLConnection,但我们确实希望在我们的测试中使用它。我们应该怎么做?我们可以调动 NSURLConnection
和FakeNSURLConnection
之间的类方法。这意味着在我们通过电话NSURLConnection connectionWithRequest:delegate
调用FakeNSURLConnection connectionWithRequest:delegate
之后。这将我们带到了我们的宏:
#define SWAP_METHODS_IMPL(REAL, FAKE) \
+ (void)swapMethods \
{ \
Method original, mock; \
unsigned int count; \
Method *methodList = class_copyMethodList(object_getClass(REAL.class), &count); \
for (int i = 0; i < count; i++) \
{ \
original = class_getClassMethod(REAL.class, method_getName(methodList[i])); \
mock = class_getClassMethod(FAKE.class, method_getName(methodList[i])); \
method_exchangeImplementations(original, mock); \
} \
free(methodList); \
}
#define DISABLE_MOCK_IMPL(FAKE) \
+ (void)disableMock \
{ \
if (_mockEnabled) \
{ \
[FAKE swapMethods]; \
_mockEnabled = NO; \
} \
}
#define ENABLE_MOCK_IMPL(FAKE) \
static BOOL _mockEnabled = NO; \
+ (void)enableMock:(id)mockObject; \
{ \
if (!_mockEnabled) \
{ \
[FAKE setSharedInstance:mockObject]; \
[FAKE swapMethods]; \
_mockEnabled = YES; \
} \
else \
{ \
[FAKE disableMock]; \
[FAKE enableMock:mockObject]; \
} \
}
#define SHARED_INSTANCE_IMPL() \
+ (id)sharedInstance \
{ \
return _sharedInstance; \
}
#define SET_SHARED_INSTANCE_IMPL() \
+ (void)setSharedInstance:(id)sharedInstance \
{ \
_sharedInstance = sharedInstance; \
}
我推荐这样的东西,这样你就不会不小心重新调整你的课程方法。那你怎么用呢?
id urlConnectionMock = [OCMockObject niceMockForClass:FakeNSURLConnection.class];
[FakeNSURLConnection enableMock:urlConnectionMock];
[_mocksToDisable addObject:FakeNSURLConnection.class];
[[[urlConnectionMock expect] andReturn:urlConnectionMock] connectionWithRequest:OCMOCK_ANY delegate:OCMOCK_ANY];
几乎就是这样 - 你已经调整了这些方法,所以你的假类会被调用,这会调用你的模拟。
啊,但最后一件事。 _mocksToDisable是一个NSMutableArray
,它将为我们已经调整过的每个类都包含一个类对象。
- (void)tearDown
{
for (id mockToDisable in _mocksToDisable)
{
[mockToDisable disableMock];
}
}
我们在tearDown
中执行此操作,以确保在测试运行后我们的课程未经调整 - 请勿在测试中正确执行此操作,因为如果有异常并非所有测试代码都可能得到执行但泪水总是会。
可能有其他模拟技术使这更简单,虽然我发现它并不是那么糟糕,因为你写了一次并且可以多次使用它。
答案 1 :(得分:0)
也许我在这里遗漏了一些东西(我知道这有点旧),但是你的班级签名是+(BOOL)getBoolVal { return YES; }
,在你的考试中,你在getBoolVal:nil
那些不匹配,对吗?在那种情况下,你的班级模拟会说,“哦,这不是我期望的签名”,并试图将其传递给underyling类,我相信。请参阅OCMock source中的forwardInvocationForClassObject
。
至于为什么你得到NO(因为基础类也返回YES,这使得这个测试没有实际意义,但这是另一个问题),我不是百分百肯定,但也许它只是一个C- ism - 'indeterminate value,void,0(false / NO)“