Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KVC via keyPath is not working with OCMock #152

Closed
michaelochs opened this issue Oct 27, 2014 · 3 comments
Closed

KVC via keyPath is not working with OCMock #152

michaelochs opened this issue Oct 27, 2014 · 3 comments

Comments

@michaelochs
Copy link

I have a object Controller that has a property that returns a mock object. I then ask Controller for valueForKeyPath: where the path is a path to the mock object's property. I can see in the debugger that valueForKeyPath: is correctly executed on the Controller and that it calls valueForKey: on the Controller object which returns the mocked object. However, the mocked object never gets a call to valueForKeyPath: or valueForKey:.

This is my mock creation:

self.reservationMock = [OCMockObject niceMockForClass:[Reservation class]];
self.sut = [[Controller alloc] initWithReservation:self.reservationMock];

The mock is stored in reservation. I evaluate a predicate with reservation.reservationMode on the sut.

I get the following calls:

  • valueForKeyPath:@"reservation.reservationMode"
  • valueForKey:@"reservation"

And that's it. If I do not mock the object, the evaluation continues with the following calls on the Reservation object.

  • valueForKeyPath:@"reservationMode"
  • valueForKey:@"reservationMode"
@erikdoe
Copy link
Owner

erikdoe commented Nov 4, 2014

This does look suspicious. I'm not sure I completely understand. Could you provide a failing test?

michaelochs pushed a commit to michaelochs/ocmock that referenced this issue Nov 10, 2014
@michaelochs
Copy link
Author

Please see the referenced commit! Internally NSPredicate uses valueForKeyPath: to fetch the desired properties, but this does not work.

@erikdoe
Copy link
Owner

erikdoe commented Feb 24, 2015

So, I keep coming back to this issue but I can't seem to find a good answer. My thoughts so far:

  • For this to work in a nice way the mock would have to provide implementations of valueForKey: and valueForKeyPath:.
  • It doesn't seem like a good idea to put these methods into OCClassMockObject because this would make it impossible to mock these methods.
  • A solution would be to create a new subclass, maybe OCClassMockObjectWithKVSupport, but that makes the API unwieldy, and it doesn't solve the questions for partial mocks. Remember, they are a subclass of normal class mocks.
  • We could have a addKeyValueCodingImplementation method that creates a dynamic subclass of the mock and adds the methods at runtime. This would address the previous two concerns. However, if we do this, what is the correct implementation of valueForKeyPath: and, especially, valueForKey:?
  • We could steal the implementation of those methods from NSObject. In fact, I've tried this (see below) and it works for valueForKeyPath: but unfortunately the implementation of valueForKey: on NSObject does depend on other private methods, e.g. +_createValueGetterWithContainerClassID:key:. Adding those methods feels too much like a cat an mouse game to me.

For reference, my implementation that "steals" the KV methods from NSObject:

- (void)addKeyValueCodingImplementation
{
    /* dynamically create a subclass and set it as the class of this mock object */
    Class subclass = OCMCreateSubclass([self mockObjectClass], self);
    object_setClass(self, subclass);

    /* add NSObject's implementation of valueForKey: and valueForKeyPath: to new subclass */
    Method vfkMethod = class_getInstanceMethod([NSObject class], @selector(valueForKey:));
    const char *vfkTypes = method_getTypeEncoding(vfkMethod);
    IMP vfkImp = method_getImplementation(vfkMethod);
    class_addMethod(subclass, @selector(valueForKey:), vfkImp, vfkTypes);

    Method vfkpMethod = class_getInstanceMethod([NSObject class], @selector(valueForKeyPath:));
    const char *vfkpTypes = method_getTypeEncoding(vfkpMethod);
    IMP vfkpImp = method_getImplementation(vfkpMethod);
    class_addMethod(subclass, @selector(valueForKeyPath:), vfkpImp, vfkpTypes);
}

At this point I feel I'm running out of ideas beyond what we discussed in #68. I'm going to close this issue but feel free to re-open it if you have a new idea.

@erikdoe erikdoe closed this as completed Feb 24, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants