-
Notifications
You must be signed in to change notification settings - Fork 607
Allow partial mocks of NSManagedObject subclasses #181
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
Conversation
static Class managedObjectClass = nil; | ||
static dispatch_once_t onceToken; | ||
dispatch_once(&onceToken, ^{ | ||
managedObjectClass = NSClassFromString(@"NSManagedObject"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how I feel about hardcoding in support for NSManagedObject
. On one hand, it's very one-off. On the other hand, it's the least "clever' way to figure out when to reach for the class via the runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NSManagedObject
seems like such a special snowflake of iOS, it probably makes sense to special-case it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a reason why object_getClass() should not be used always? If we are mocking something, seems like we should be invoking methods on the actual class no matter what.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's been a while, but I recall not doing this was messing with KVO notifications + the associated tests. KVO dynamically subclasses your class to provide automatic notifications, and something about then re-subclassing that class wasn't working.
NSEntityDescription *const entity = model.entitiesByName[NSStringFromClass([OCTestManagedObject class])]; | ||
NSPersistentStoreCoordinator *const coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; | ||
[coordinator addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:NULL]; | ||
NSManagedObjectContext *const context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing autorelease (assuming the tests aren't ARC either).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Nvm, tests do indeed use ARC.)
Is anybody going to merge this PR? |
This PR is still in the queue. The change itself is small, namely using the |
To expand on my earlier comment: We can't do
However, for Core Data, it works by:
Does that help? |
Okay, that clarifies a bit. I understand the need for using Obviously I agree that using For example, let's take a method ocmock/Source/OCMock/OCPartialMockObject.m Lines 203 to 215 in 17dd24e
Unless the mock is instructed (via stub/expect/reject) to do something |
If I remember, the issue with partial mocking a KVO class is that there is some global state keyed by the object's isa (since that is a per-instance class just like partial mocks) and when you change it, the KVO mechanism can crash when you call any of the modified methods since all of the associated state is no longer found with the new isa pointer. The way it works now, the partial mocking will blow away the KVO class completely so that notifications won't work while mocked, as all of the altered methods are in a class which is no longer referenced by the object. The KVO class gets restored at -stopMocking, so KVO cleanup can work afterwards. If you partial mock first and then KVO, I think it works, provided you remove all KVO observers (thus restoring the partial mock isa) before -stopMocking is called. There is probably a unit test to make sure that things at least don't crash if you partial mock a KVO object. I guess the question is what we want to do when faced with dynamic-subclass-replace-isa-override-stubsclassmethod situations (i.e. where [object class] != object_getClass(object) ). We want to go one way with NSManagedObject and the other with KVO. The current change only alters the behavior for NSManagedObject, but doesn't change the default. Maybe though we want to check for KVO explicity and use [object class] in that case, and object_getClass() in all others (thus changing the default). Checking for [object observationInfo] != NULL should do it. The question is what happens with other such situations -- not sure there are any others used by Apple, but who knows about third party code. It's arguable though that using the dynamic subclass is usually better (as it is with NSManagedObject). |
I'm going to close this out, @alanf has a new PR that incorporates feedback. |
Followup on this issue: #163
object_getClass()
, instead of relying on-class
, which lies in this case.Not sure this is the right thing to do, but it works. Thoughts?