2

I am working on unit tests using XCTest and OCMock 2.2.1. I have a class that obtains the bundle identifier using:

NSString *bundleIdentifier = [[NSBundle bundleForClass:[self class]] bundleIdentifier];

This works as expected while running the application or the unit tests for this class in particular.

While working through tests on other classes, I am partially mocking this object but still require the method that gets the bundle identifier to run.

What I am seeing is prior to passing the instance of the object to + [OCMockObject partialMockForObject:] looks correct:

(lldb) po myObject
<MyObject: 0x1006ec480>
(lldb) po [NSBundle bundleForClass:[myObject class]]
NSBundle </Users/paynerc/Library/Developer/Xcode/DerivedData/xxxx/Build/Products/Debug/MyTests Tests.xctest> (loaded)
(lldb) po [[NSBundle bundleForClass:[myObject class]] bundleIdentifier]
com.paynerc.MyBundle

However, after I pass myObject into [OCMockObject partialMockForObject:myObject], things change:

(lldb) po myObject
<MyObject-0x1006ec480-401894396.880136: 0x1006ec480>
(lldb) po [NSBundle bundleForClass:[myObject class]]
NSBundle </Applications/Xcode.app/Contents/Developer/usr/bin> (loaded)
(lldb) po [[NSBundle bundleForClass:[myObject class]] bundleIdentifier]
nil

The fact that the object is modified and includes the partial mock magic makes sense. What doesn't seem to make sense is why the call to bundleForClass has changed what it returns.

Is there anything I can do to ensure that bundleForClass continues to return the original value, short of mocking the calls inside MyObject? The concern there is that anybody else that needs a partial mock of MyObject in another unit test will need to remember to provided a stubbed implementation of bundleForClass.

My present solution is to request the bundle identifier and examine the result. If it is nil, I am calling [NSBundle allBundles] and iterating over them until I find one that has a non-nil bundleIdentifier. While that currently... works... it's A) not very robust B) horribly brute force-ish and C) modifying application code to support unit tests.

Has anybody else come across this and come up with a better solution?

Camille G.
  • 3,058
  • 1
  • 25
  • 41

1 Answers1

3

The runtime is behaving correctly. A mocked object is a subclass of NSProxy and, thus, the runtime bond between the object's isa and the bundle is effectively broken (in particular, the isa points to the Class which is then looked up via dyld API to determine the mach-o image that it was loaded from and that is used to find the bundle).

There is likely API on the OCMockObject proxy (or subclass OCPartialMockObject) that allows you to retrieve the original class. You'll have to use that which, of course, means you'll be polluting your code with mock calls that should only be used in testing.

Alternatively, implement a class method on one of the classes in your bundle/framework/whatever that returns the bundle for the class. That shouldn't be mocked.

bbum
  • 162,346
  • 23
  • 271
  • 359
  • Bill is right as usual. However, we are changing the behaviour of the mock object so that it will do what you are expecting. This is work in progress. See https://github.com/erikdoe/ocmock/pull/45 and https://github.com/erikdoe/ocmock/commit/c7ed21cead25a78c98fd46e1f95defef4721e5e6 – Erik Doernenburg Sep 27 '13 at 14:25
  • NSProxy subclasses typically implement -isKindOfClass: to return YES for the class being proxied for, and OCClassMockObject is no different. (Though it maybe should implement -class as well, as NSProxy subclasses also usually do). The problem here though is on the original object being mocked itself -- the partial mocking process changes its "isa" pointer, and therefore the return value from the -class method is different, which in turn messes up bundleForClass:. But as Erik says, it looks like this should be fixed in the next release of OCMock. – Carl Lindberg Sep 30 '13 at 15:41
  • @CarlLindberg Thanks for the clarification; I hadn't looked at the implementation of OCMock* prior. – bbum Sep 30 '13 at 17:39