Reputation: 9693
When executing unit tests for RestKit Mapping Code with RKMappingTest
s, 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.
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
Reputation: 9693
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.
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.
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.
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.
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