如何让ARC下的OCMock停止使用弱属性来设置NSProxy子类?

时间:2012-02-01 22:47:46

标签: objective-c unit-testing automatic-ref-counting ocmock nsproxy

ARC下,我有一个Child属性weak的对象parent。我正在尝试为Child编写一些测试,我正在使用parent来模拟其OCMock属性。

在ARC下,使用合成的弱属性设置器设置NSProxy子类不设置属性...设置弱属性后的行,检查它显示它已经是nil。这是具体的例子:

@interface Child : NSObject
@property (nonatomic, weak) id <ParentInterface>parent;
@end

@implementation Child
@synthesize parent = parent_;
@end

//  ... later, inside a test class ...

- (void)testParentExists
{
    // `mockForProtocol` returns an `NSProxy` subclass
    //
    OCMockObject *aParent = [OCMockObject mockForProtocol:@protocol(ParentInterface)];
    assertThat(aParent, notNilValue());

    // `Child` is the class under test
    //
    Child *child = [[Child alloc] init];
    assertThat(child, notNilValue());

    assertThat(child.parent, nilValue());
    child.parent = (id<ParentInterface>)aParent;
    assertThat([child parent], notNilValue());  // <-- This assertion fails
    [aParent self]; // <-- Added this reference just to ensure `aParent` was valid until the end of the test.
}

我知道我可以使用assign属性而不是weak属性来解决此问题,Child引用Parent,但之后我会当我完成它之后nil parent(就像某种穴居人一样),这正是ARC应该避免的那种事情。

有关如何在不更改应用代码的情况下通过此测试的任何建议?

修改:如果我OCMockObject成为NSProxy的实例,那么aParent似乎与NSObject有关, child.parent弱引用“保持”非零值。仍在寻找一种方法来使测试通过,而无需更改应用程序代码。

编辑2 :在接受Blake的回答之后,我在我的预处理器宏项目中做了一个实现,它有条件地将我的属性从弱 - &gt;分配。您的里程可能会有所不同:

#if __has_feature(objc_arc)
#define BBE_WEAK_PROPERTY(type, name) @property (weak, nonatomic) type name
#else
#define BBE_WEAK_PROPERTY(type, name) @property (assign, nonatomic) type name
#endif

3 个答案:

答案 0 :(得分:9)

我们一直在努力解决同样的问题,它确实与ARC和NSProxy派生对象的弱引用之间的不兼容性有关。我建议使用预处理器指令有条件地编译您的弱委托引用以在测试套件中进行分配,以便您可以通过OCMock进行测试。

答案 1 :(得分:6)

我找到了一个与条件宏不同的解决方案,因为我测试的是无法更改代码的代码。

我编写了一个简单的类,它扩展了NSObject,而不是NSProxy,它将所有选择器调用转发到OCMockProxy。

CCWeakMockProxy.h:

#import <Foundation/Foundation.h>

/**
 * This class is a hack around the fact that ARC weak references are immediately nil'd if the referent is an NSProxy
 * See: http://stackoverflow.com/questions/9104544/how-can-i-get-ocmock-under-arc-to-stop-nilling-an-nsproxy-subclass-set-using-a-w
 */
@interface CCWeakMockProxy : NSObject

@property (strong, nonatomic) id mock;

- (id)initWithMock:(id)mockObj;

+ (id)mockForClass:(Class)aClass;
+ (id)mockForProtocol:(Protocol *)aProtocol;
+ (id)niceMockForClass:(Class)aClass;
+ (id)niceMockForProtocol:(Protocol *)aProtocol;
+ (id)observerMock;
+ (id)partialMockForObject:(NSObject *)anObject;

@end

CCWeakMockProxy.m:

#import "CCWeakMockProxy.h"
#import <OCMock/OCMock.h>


#pragma mark Implementation
@implementation CCWeakMockProxy

#pragma mark Properties
@synthesize mock;

#pragma mark Memory Management
- (id)initWithMock:(id)mockObj {
    if (self = [super init]) {
        self.mock = mockObj;
    }
    return self;
}

#pragma mark NSObject
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return self.mock;
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [self.mock respondsToSelector:aSelector];
}

#pragma mark Public Methods
+ (id)mockForClass:(Class)aClass {
    return [[CCWeakMockProxy alloc] initWithMock:[OCMockObject mockForClass:aClass]];
}

+ (id)mockForProtocol:(Protocol *)aProtocol {
    return [[CCWeakMockProxy alloc] initWithMock:[OCMockObject mockForProtocol:aProtocol]];
}

+ (id)niceMockForClass:(Class)aClass {
    return [[CCWeakMockProxy alloc] initWithMock:[OCMockObject niceMockForClass:aClass]];
}

+ (id)niceMockForProtocol:(Protocol *)aProtocol {
    return [[CCWeakMockProxy alloc] initWithMock:[OCMockObject niceMockForProtocol:aProtocol]];
}

+ (id)observerMock {
    return [[CCWeakMockProxy alloc] initWithMock:[OCMockObject observerMock]];
}

+ (id)partialMockForObject:(NSObject *)anObject {
    return [[CCWeakMockProxy alloc] initWithMock:[OCMockObject partialMockForObject:anObject]];
}

@end

只需使用生成的对象,就像使用常规的OCMockObject一样!

答案 2 :(得分:0)

不确定。它正在nil,因为在分配child.parent之后,您的代理对象本身会被您的测试释放(因为它不再被引用),这会导致弱引用为nil out。因此,解决方案是在测试期间保持代理对象的活动状态。您可以通过插入

来轻松完成此操作
[aParent self];

在您的方法结束时。该函数调用不执行任何操作(-self只返回self),但它将确保ARC使对象保持活动状态。

另一种方法是将aParent的声明更改为__autoreleasing,这使得它更像MRR,因为ARC只会在该插槽中留下自动释放的引用,而不是显式释放对象当变量超出范围时。你可以用

做到这一点
__autoreleasing OCMockObject *aParent = ...

那就是说,第一个解决方案可能更清晰,因为你在测试过程中明确地保持了对象的存活。