使用OCMock版本2.1.1测试类方法

时间:2013-05-09 11:59:52

标签: iphone ios unit-testing ocmock stubbing

我正在尝试检查是否使用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,它也会成功。在该方法上保留断点时,测试执行不会停止,表明未调用该方法。

如何查看课程方法?

2 个答案:

答案 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,但我们确实希望在我们的测试中使用它。我们应该怎么做?我们可以调动 NSURLConnectionFakeNSURLConnection之间的类方法。这意味着在我们通过电话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)“