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

时间:2023-01-26 15:11:17

I'm writing a test to verify location services are started when a button click occurs. This requires a very simple if statement to make sure the phone has location services available.

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

A working test right now looks like this

现在正在进行的工作测试看起来像这样

- (void)testStartUpdatingLocationInvokedWhenLocationServicesAreEnabled
{
    [[[self.locationManager stub] andReturnValue:[NSNumber numberWithBool:true]] locationServicesEnabled];
    [[self.locationManager expect] startUpdatingLocation];
    [self.sut buttonClickToFindLocation:nil];
    [self.locationManager verify];
}

The now tested implementation looks like this

现在经过测试的实现看起来像这样

- (IBAction)buttonClickToFindLocation:(id)sender
{
    if ([self.locationManager locationServicesEnabled])
    {
        [self.locationManager startUpdatingLocation];
    }
}

All good except the method was deprecated in iOS 4.0. So now I need to use the Class Method [CLLocationManager locationServicesEnabled] instead.

除了方法之外的所有好处都在iOS 4.0中弃用。所以现在我需要使用类方法[CLLocationManager locationServicesEnabled]。

The problem is I can't seem to find if ocmock supports this functionality and if it doesn't how should I get around this issue for now.

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

4 个解决方案

#1


3  

hmmm, you could use methodExchange. Just make sure you exchange the method back to original after your done with it. It seems hacky, but I haven't found a better solution. I have done something similar for stubbing [NSDate date]

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

@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

EDIT: I thought you were verifying, but it seems like you are stubbing. Updated code

编辑:我以为你在验证,但似乎你在瞎扯。更新的代码

#2


2  

The simplest approach is to override locationServicesEnabled in a category in your unit test class:

最简单的方法是在单元测试类的类别中覆盖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];
}

It will override the superclass method only at test time, and you can set the static global to an appropriate value in each test.

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

Alternatively, you could wrap the check in your own instance method, and use a partial mock.

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

In the class under test:

在被测试的班级中:

-(BOOL)locationServicesEnabled {
    return [CLLocationManager locationServicesEnabled];
}

In your test:

在你的测试中:

-(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


1  

I don't think it does. The only approach I can think of would be to use a partial mock and then use runtime calls to swizzle in the implementation you need.

我不认为这样。我能想到的唯一方法是使用部分mock,然后使用运行时调用来调整你需要的实现。

Doable, but complex.

可行但复杂。

A more pattern orientated solution might be to extract the checking for location services out to sit behind a protocol. Then you can simply use a mock for the protocol's implementation during testing to return YES or NO as your require. As the actual implementation would do nothing but return [CLLocationManager locationServicesEnabled] you could get away with not testing it.

更加面向模式的解决方案可能是将位置服务的检查提取出来以支持协议。然后,您可以在测试期间使用模拟执行协议的实现,以根据需要返回YES或NO。由于实际的实现除了返回[CLLocationManager locationServicesEnabled]之外什么都不做,你可以逃避不测试它。

#4


0  

This is supported by OCMock:

这得到了OCMock的支持:

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

#1


3  

hmmm, you could use methodExchange. Just make sure you exchange the method back to original after your done with it. It seems hacky, but I haven't found a better solution. I have done something similar for stubbing [NSDate date]

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

@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

EDIT: I thought you were verifying, but it seems like you are stubbing. Updated code

编辑:我以为你在验证,但似乎你在瞎扯。更新的代码

#2


2  

The simplest approach is to override locationServicesEnabled in a category in your unit test class:

最简单的方法是在单元测试类的类别中覆盖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];
}

It will override the superclass method only at test time, and you can set the static global to an appropriate value in each test.

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

Alternatively, you could wrap the check in your own instance method, and use a partial mock.

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

In the class under test:

在被测试的班级中:

-(BOOL)locationServicesEnabled {
    return [CLLocationManager locationServicesEnabled];
}

In your test:

在你的测试中:

-(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


1  

I don't think it does. The only approach I can think of would be to use a partial mock and then use runtime calls to swizzle in the implementation you need.

我不认为这样。我能想到的唯一方法是使用部分mock,然后使用运行时调用来调整你需要的实现。

Doable, but complex.

可行但复杂。

A more pattern orientated solution might be to extract the checking for location services out to sit behind a protocol. Then you can simply use a mock for the protocol's implementation during testing to return YES or NO as your require. As the actual implementation would do nothing but return [CLLocationManager locationServicesEnabled] you could get away with not testing it.

更加面向模式的解决方案可能是将位置服务的检查提取出来以支持协议。然后,您可以在测试期间使用模拟执行协议的实现,以根据需要返回YES或NO。由于实际的实现除了返回[CLLocationManager locationServicesEnabled]之外什么都不做,你可以逃避不测试它。

#4


0  

This is supported by OCMock:

这得到了OCMock的支持:

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