我想测试Key-Value-Observation是否适用于我的一类。它有一个属性,这取决于另一个属性。它们的设置如下:
+ (NSSet *)keyPathsForValuesAffectingSecondProperty {
return [NSSet setWithObjects:
@"firstProperty",
nil];
}
- (NSArray *)secondProperty {
return [self.firstProperty array];
}
我想运行单元测试来验证当firstProperty
更改时,绑定到secondProperty
的对象会收到通知。起初我以为我可以使用+[OCMockObject observerMock]
,但看起来只能与NSNotificationCenter
一起使用。测试这个的最佳方法是什么?
答案 0 :(得分: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
的值都失败,并且堆栈中存在NSKeyValueWillChange的EXC_BAD_ACCESS
异常。
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"];
答案 1 :(得分: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];
答案 2 :(得分: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"];
}
适合我的工作:)