Calling + [NSBundle bundleForClass:] on a partially mocked object is returning a different result than the unmocked object?

StackOverflow https://stackoverflow.com/questions/19030194

  •  29-06-2022
  •  | 
  •  

Pregunta

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?

¿Fue útil?

Solución

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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top