【问题标题】:Mocking KVO with OCMock使用 OCMock 模拟 KVO
【发布时间】:2012-03-07 14:33:26
【问题描述】:

我想测试一下 Key-Value-Observation 是否适用于我的一类。它有一个属性,它依赖于另一个属性。它们的设置如下:

+ (NSSet *)keyPathsForValuesAffectingSecondProperty {
    return [NSSet setWithObjects:
            @"firstProperty",
            nil];
}

- (NSArray *)secondProperty {
    return [self.firstProperty array];
}

我想运行一个单元测试来验证当firstProperty 更改时,绑定到secondProperty 的对象会收到通知。起初我以为我可以使用+[OCMockObject observerMock],但看起来只能与NSNotificationCenter 一起使用。对此进行测试的最佳方法是什么?

【问题讨论】:

    标签: objective-c cocoa unit-testing key-value-observing ocmock


    【解决方案1】:

    在@chrispix 的回答启发我朝着不同的方向工作之后,我做了一段时间的工作。我从这个开始:

    id objectToObserve = [[TheClassBeingTested alloc] init];
    
    id secondPropertyObserver = [OCMockObject mockForClass:[NSObject class]];
    [[secondPropertyObserver expect] observeValueForKeyPath:@"secondProperty"
                                                   ofObject:objectToObserve
                                                     change:OCMOCK_ANY
                                                    context:[OCMArg anyPointer]];
    [objectToObserve addObserver:secondPropertyObserver
                      forKeyPath:@"secondProperty"
                         options:NSKeyValueObservingOptionNew
                         context:NULL];
    
    // Do something to modify objectToObserve's firstProperty    
    
    [secondPropertyObserver verify];
    

    当我运行此测试代码时,我收到以下消息:

    OCMockObject[NSObject]: unexpected method invoked: isKindOfClass:<??> 
        expected:     observeValueForKeyPath:@"firstProperty" ofObject:
    

    我做了一些调查,发现 -isKindOfClass: 调用模拟对象没想到被传递给 NSKeyValueObservance 类对象。

    我尝试添加以下代码来模拟响应,但 YESNO 的值都失败,出现 EXC_BAD_ACCESS 异常,堆栈中有 NSKeyValueWillChange。

    BOOL returnVal = NO;
    [[[secondPropertyObserver stub] andReturnValue:OCMOCK_VALUE(returnVal)] isKindOfClass:[OCMArg any]];
    

    我走得更仔细了,发现我的代码并没有导致这个异常 - 这是在 autoreleasepool 被耗尽的时候。然后我突然意识到我需要移除观察者。下面是完整的解决方案,包括移除观察者。

    id objectToObserve = [[TheClassBeingTested alloc] init];
    
    id secondPropertyObserver = [OCMockObject mockForClass:[NSObject class]];
    
    BOOL returnVal = NO;
    [[[secondPropertyObserver stub] andReturnValue:OCMOCK_VALUE(returnVal)] isKindOfClass:[OCMArg any]];
    
    [[secondPropertyObserver expect] observeValueForKeyPath:@"secondProperty"
                                                   ofObject:objectToObserve
                                                     change:OCMOCK_ANY
                                                    context:[OCMArg anyPointer]];
    
    [objectToObserve addObserver:secondPropertyObserver
                      forKeyPath:@"secondProperty"
                         options:NSKeyValueObservingOptionNew
                         context:NULL];
    
    // Do something to modify objectToObserve's firstProperty    
    
    [secondPropertyObserver verify];
    
    [objectToObserve removeObserver:secondPropertyObserver
                         forKeyPath:@"secondProperty"];
    

    【讨论】:

      【解决方案2】:

      如果secondProperty 收到您可以验证的通知有一些结果,我只需将其设置为实际对象并在之后验证状态。如果这不可能,您可以创建一个 secondProperty 的部分模拟并期待 KVO 回调:

      id observedObject = // ...initialize observed class
      observedObject.secondProperty = // initialize secondProperty
      id mockSecondProperty = [OCMockObject partialMockForObject:observedObject.secondProperty];
      [[mockSecondProperty expect] observeValueForKeyPath:@"firstProperty" ofObject:observedObject change:OCMOCK_ANY context:OCMOCK_ANY];
      
      observedObject.firstProperty = // modify firstProperty
      
      [mockSecondProperty verify];
      

      【讨论】:

        【解决方案3】:

        仅适用于对此感兴趣的人:这是一个没有 OCMock 的解决方案。

        我只是将希望更改的值绑定到我的单元测试类的测试属性。

        在以下示例中,我希望更新 numberOfPages 属性并在更改 pages 数组时触发 KVO 通知。

        我在 DocumentTests 类上声明了一个 boundInteger 属性:

        @interface DocumentTests : SenTestCase 
        @property (assign) int boundInteger;
        @end
        

        然后我像这样实现我的测试用例:

        - (void)testNumberOfPages
        {
            Document *doc = [[Document alloc] init];
        
            [self bind:@"boundInteger" toObject:doc withKeyPath:@"numberOfPages" options:nil];
        
            doc.pages = arrayOfThreePagesIHaveBuildSomewhereElse;
        
            STAssertEquals(self.boundInteger, 3, @"Wrong number of pages.");
            STAssertEquals(doc.numberOfPages, 3, @"Wrong number of pages.");
        
            [self unbind:@"boundInteger"];
        }
        

        对我来说很好:)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-04-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多