4

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

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

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

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

4

3 回答 3

7

在@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类对象。

我尝试添加以下代码来模拟响应,但是YES和的值NO都失败,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"];
于 2012-03-08T13:24:54.480 回答
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];
于 2012-03-07T18:06:02.657 回答
1

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

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

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

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

@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"];
}

对我来说很好:)

于 2012-07-27T09:38:00.233 回答