单元测试一个依赖于NSMapTable来清理缺少强引用的对象的方法

时间:2014-06-04 22:11:24

标签: objective-c unit-testing ocmock nsautoreleasepool

所以我有以下方法(它是一个UIView类别方法来补充nib加载,但是,它已被清理为更相关):

+ (id) loadFromNib {

    NSString* nibName = NSStringFromClass([self class]);
    NSArray* elements = [[NSBundle mainBundle] loadNibNamed:nibName owner:nil options:nil];

    NSMutableArray* foundCustomObjects = [NSMutableArray array];
    NSObject* foundViewObject = nil;
    for (NSObject* anObject in elements) {

        if ([anObject isKindOfClass:[self class]] && foundViewObject == nil) {

            foundViewObject = anObject;

        // Keep strong references to non-UIView custom objects (to prevent them from being released due to having weak-only references):
        } else if (![anObject isKindOfClass:[UIView class]]) {

            [foundCustomObjects addObject:anObject];
        }
    }

    // Generate strong references to all found custom objects:
    if (foundViewObject != nil) {
        [foundCustomObjects enumerateObjectsUsingBlock:
         ^(id obj, NSUInteger idx, BOOL *stop) {

            [customObjects setObject:foundViewObject forKey:obj];

            // (Yes, I will skip objects that are strongly referenced by their view later on)
        }];
    }

    return foundViewObject;
}

customObjects是一个静态变量,定义为:

+ (void) initialize {

    if (customObjects == nil) {

        // For each view that holds a custom object, store a strong reference to that object here, that way preventing the object from being deallocated due to weak referencing (in UICollectionView.delegate, for example):
        customObjects = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory];
    }
}

我的问题是我想要对已解除分配的视图真正导致释放引用的“自定义对象”的事实进行单元测试。我该怎么做?

这是我到目前为止(使用OCMock):

- (void) test {

    /*
     * SETUP */

    NSObject* __weak weakRefToSomeObject;
    UIView* someView;
    NSObject* someObject;

    @autoreleasepool {

        someView = [[UIView alloc] init];
        someObject = [[NSObject alloc] init];

        NSArray* nibElements = @[someView, someObject];

        id mainBundleMock = [OCMockObject niceMockForClass:[NSBundle class]];
        [[[mainBundleMock stub] andReturn:nibElements] loadNibNamed:[OCMArg any] owner:[OCMArg any] options:[OCMArg any]];

        id NSBundleMock = [OCMockObject niceMockForClass:[NSBundle class]];
        [[[NSBundleMock stub] andReturn:mainBundleMock] mainBundle];

    /*
     * RUN */

        weakRefToSomeObject = someObject;

        [UIView loadFromNib];

        someObject = nil;
        nibElements = nil;

        [mainBundleMock stopMocking];
        [NSBundleMock stopMocking];

        mainBundleMock = nil;
        NSBundleMock = nil;

    }

    /*
     * VERIFY */

    XCTAssertNotNil(weakRefToSomeObject); // This passes!

    @autoreleasepool {
        someView = nil;
    }

    XCTAssertNil(weakRefToSomeObject); // This does not pass - why?
}

在最后一行,我希望删除键值对(视图被弱引用),这样就可以删除对someObject的最后一个强引用,从而使weakRefToSomeObject为零。

我还尝试将someView = nil添加到第一个autoreleasepool(就在NSBundleMock = nil之下),但这没有帮助。

有什么想法吗?

1 个答案:

答案 0 :(得分:0)

我通过添加访问功能修复了这个问题:

// Allow tests to access the customObjects map:
NSMapTable* getCustomObjectsMap() {

    return customObjects;
}

然后在我的单元测试文档中声明:

// Declare method that gives us access to the static customObjects variable:
NSMapTable* getCustomObjectsMap();

因此,测试代码最终成为:

- (void) testCustomObjectLifecycleFromStartToFinish {

    /*
     * ASSERT REQUIRED INITIAL STATE */

    XCTAssertNil([[getCustomObjectsMap() objectEnumerator] nextObject]);

    /*
     * SETUP */

    UIView* someView = [[UIView alloc] init];
    NSObject* someObject = [[NSObject alloc] init];

    @autoreleasepool {

        @autoreleasepool {

            NSArray* nibElements = @[someView, someObject];

            id mainBundleMock = [OCMockObject niceMockForClass:[NSBundle class]];
            [[[mainBundleMock stub] andReturn:nibElements] loadNibNamed:[OCMArg any] owner:[OCMArg any] options:[OCMArg any]];

            id NSBundleMock = [OCMockObject niceMockForClass:[NSBundle class]];
            [[[NSBundleMock stub] andReturn:mainBundleMock] mainBundle];

    /*
     * RUN */

            [UIView loadFromNib];

            someObject = nil;
            nibElements = nil;
        }

    /*
     * VERIFY */

        XCTAssertNotNil([[getCustomObjectsMap() objectEnumerator] nextObject]);

        // Dropping last strong reference to view:
        someView = nil;
    }

    // Without strong references to someView, the objects map should have been emptied:
    XCTAssertNil([[getCustomObjectsMap() objectEnumerator] nextObject]);
}