Tim Bodeit
Tim Bodeit

Reputation: 9693

RestKit & Cocoapods: Mapping operation failed / did not find any mappable values

When executing unit tests for RestKit Mapping Code with RKMappingTests, they fail with exceptions such as:

Mapping operation failed: Given nil destination object and unable to instantiate a destination object for mapping.

or (when a destination object is passed into the RKMappingTest):

Mapping operation did not find any mappable values for the attribute and relationship mappings in the given object representation


RestKit is installed through Cocoapods using the following Podfile:

target :MyTarget do
    pod 'RestKit'
end

target :MyTargetTests do
    pod 'RestKit/Testing'
    pod 'RestKit/Search'
end

The RKMapping to be tested is created inside the regular application bundle and turns out properly when using the po command of the lldb debugger.

When the method for generating the RKEntityMapping is copy-pasted into the unit test class and executed there, everything is working properly.


Note:

Though I did answer my own question in the hope that it would be useful for somebody else with the same problem, I encourage anybody who can come up with a better solution to post it.

Upvotes: 1

Views: 333

Answers (1)

Tim Bodeit
Tim Bodeit

Reputation: 9693

Reason why this happens:

Disclaimer:
I am no expert on static libraries, linking and related dependencies. If I got something wrong about the way that linking or Cocoapods work, please correct me.

For this Podfile, Cocoapods builds two static libraries – one for each target.

When the project is compiled, all the code in MyTarget will get linked against the LibPods-MyTarget.a library and all the code in MyTargetTests will get linked against the LibPods-MyTargetTests.a library.
Both of them contain a copy of the RestKit/ObjectMapping component (since it is a dependency of both RestKit and RestKit/Testing).

When generating a RKMapping in the application source (MyTarget), the class implementation from LibPods-MyTarget.a is used.
When using that mapping inside the unit test class, the RestKit implementation is linked from the other library.

Theoretically, both implementations contain the same source code, both classes have the same name, the same class number and probably even the underlying objc_class structs contain the same content.

When you call a method on it from the test class, it will execute as it should (with implementation coming from the apps library). Saying: depending on where the object was alloc-inited, equal source code will be loaded from a different copy at a different memory location.

However, since the Objective-C Class construct is higher level than the underlying objc_class struct, they are not both the same.


Example of what this means:

Let's assume we have have a class called MyObject, that includes a method with the following signature:

+ (RKObjectMapping*)generateMapping;

While the method itself works perfectly fine, this test would fail:

- (void)testClassEquality {
    RKEntityMapping *mappingFromAppBundle = [MyObject generateMapping];
    Class testBundleMappingClass = [RKMapping class];
    
    XCTAssert([mappingFromAppBundle isMemberOfClass:testBundleMappingClass],
              @"Mapping class from app bundle doesn't match Mapping class from test bundle");
}

Because:

[RKMapping class] from LibPods-MyTarget.a
!=
[RKMapping class] from LibPods-MyTargetTests.a


RestKit heavily relies on isMemberOfClass: and isSubclassOf: operations for implementing its functionality. Therefore this kind of duplicate implementation breaks it.


Solutions:

Quick and dirty:

Don't use two different Cocoapod configurations for the two targets. Use a simple Podfile such as:

pod 'RestKit'    
pod 'RestKit/Testing'
pod 'RestKit/Search'

Then use the same library/configuration for both targets. The linker link against the same copy for both targets.

Dead-code-stripping should prevent the unneeded code from being included in your final application.

However: It will still need to be compiled on each build and if you have the -ObjC or -all_load linker flags set, the unused code will be shipping with your app.

Even dirtier:

I strongly advise against this, since it pretty much defeats the purpose of a unit test. I've seen this as another workaround and included it for the sake of completion:

Copy and paste the implementation of the code into the unit test class and use it from there.

Suggestion:

Cocoapods makes a static library for every single component first and combines them into a bigger library for each target, project or workspace.

It should be possible, to link the small libraries directly into the target without using the big union. By doing this, without automation, one would however kill the automatic dependency management, that Cocoapods was built for.

If anybody has some time on their hands and implements a script for this or modifies Cocoapods to add functionality, please let me know.


Other solutions?

If I'm missing a functionality that Cocoapods already has or you know any additional solutions, please comment or post it as another answer.

Upvotes: 2

Related Questions