Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking KVO with OCMock

I would like to test that Key-Value-Observation is working properly for a class of mine. It has one property, which depends on another. They are set up like so:

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

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

I want to run a unit test to verify that when firstProperty changes, an object bound to secondProperty gets a notification. At first I thought I would be able to use +[OCMockObject observerMock], but it looks like that can only be used with NSNotificationCenter. What would be the best way to test this?

like image 803
Dov Avatar asked Dec 27 '22 04:12

Dov


1 Answers

I worked on this for a while after @chrispix's answer inspired me to work in a different direction. I started with this:

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];

When I ran this test code, I got the following message:

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

I did some investigation and found that the -isKindOfClass: call the mock object didn't expect was being passed an NSKeyValueObservance class object.

I tried adding the following code to mock a response, but values of YES and NO both fail with EXC_BAD_ACCESS exceptions with NSKeyValueWillChange in the stack.

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

I stepped more carefully and found that my code wasn't causing this exception - it was while the autoreleasepool was being drained. It then dawned on me that I needed to remove the observer. Below is the complete solution, including removing the observer.

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"];
like image 125
Dov Avatar answered Jan 19 '23 06:01

Dov