如何使用 ocmock 在基于类的方法上存根返回值

Posted

技术标签:

【中文标题】如何使用 ocmock 在基于类的方法上存根返回值【英文标题】:How to stub out the return value on a class based method using ocmock 【发布时间】:2012-03-25 21:53:50 【问题描述】:

我正在编写一个测试来验证位置服务是否在单击按钮时启动。这需要一个非常简单的 if 语句来确保手机有可用的定位服务。

现在的工作测试如下所示

- (void)testStartUpdatingLocationInvokedWhenLocationServicesAreEnabled

    [[[self.locationManager stub] andReturnValue:[NSNumber numberWithBool:true]] locationServicesEnabled];
    [[self.locationManager expect] startUpdatingLocation];
    [self.sut buttonClickToFindLocation:nil];
    [self.locationManager verify];

现在测试的实现如下所示

- (IBAction)buttonClickToFindLocation:(id)sender

    if ([self.locationManager locationServicesEnabled])
    
        [self.locationManager startUpdatingLocation];
    

除了方法是deprecated in ios 4.0之外,一切都很好。所以现在我需要使用类方法 [CLLocationManager locationServicesEnabled]。

问题是我似乎无法找到 ocmock 是否支持此功能,如果不支持,我现在应该如何解决此问题。

【问题讨论】:

【参考方案1】:

嗯,你可以使用方法交换。只需确保在完成后将方法交换回原始方法即可。看起来很hacky,但我还没有找到更好的解决方案。我为存根 [NSDate date] 做了类似的事情

@implementation

static BOOL locationManagerExpectedResult;

- (void)testStartUpdatingLocationInvokedWhenLocationServicesAreEnabled

    locationManagerExpectedResult = YES;

    method_exchangeImplementations(
       class_getClassMethod([CLLocationManager class], @selector(locationServicesEnabled)) , 
       class_getClassMethod([self class], @selector(locationServicesEnabledMock))
    );

    [self.sut buttonClickToFindLocation:nil];


+ (BOOL)locationServicesEnabledMock

    return locationManagerExpectedResult;


@end

编辑:我以为你在验证,但看起来你在存根。更新代码

【讨论】:

对于其他遵循 -note- 的人,您需要添加此导入 #​​import 【参考方案2】:

最简单的方法是在单元测试类的一个类别中覆盖locationServicesEnabled

static BOOL locationServicesEnabled = NO;

@implementation CLLocationManager (UnitTests)

+(BOOL)locationServicesEnabled 
    return locationServicesEnabled;


@end

...

-(void)tearDown 
    // reset to default after each test
    locationServicesEnabled = NO;
    [super tearDown];

它只会在测试时覆盖超类方法,并且您可以在每个测试中将静态全局设置为适当的值。

或者,您可以将检查包装在您自己的实例方法中,并使用部分模拟。

在被测类中:

-(BOOL)locationServicesEnabled 
    return [CLLocationManager locationServicesEnabled];

在你的测试中:

-(void)testSomeLocationThing 
    MyController *controller = [[MyController alloc] init];
    id mockController = [OCMockObject partialMockForObject:controller];
    BOOL trackingLocation = YES;
    [[[mockController stub] andReturnValue:OCMOCK_VALUE(trackingLocation)] locationServicesEnabled];

    // test your controller ...

【讨论】:

【参考方案3】:

我认为不会。我能想到的唯一方法是使用部分模拟,然后使用运行时调用来调动您需要的实现。

可行,但复杂。

一个更面向模式的解决方案可能是将位置服务检查提取到协议后面。然后,您可以在测试期间简单地对协议的实现使用模拟,以根据您的要求返回 YES 或 NO。由于实际实现只会返回[CLLocationManager locationServicesEnabled],因此您可以不对其进行测试。

【讨论】:

【参考方案4】:

OCMock 支持此功能:

[[[[mockLocationManagerClass stub] classMethod] andReturnValue:OCMOCK_VALUE(YES)] locationServicesEnabled];

【讨论】:

以上是关于如何使用 ocmock 在基于类的方法上存根返回值的主要内容,如果未能解决你的问题,请参考以下文章

正在测试的类的 OCMock 存根类方法

所有调用的 OCMock 存根类方法

在 OCMPartialMock 上存根类方法?

OCMock 不能存根分类方法

不能在 Xcode 5.0 中使用 OCMock 2.1+ 存根类方法

OCMock: OCMPartialMock 对象调用实际方法而不是存根