5

When I set the return value of an NSInvocation to be an NSString, the invoker is receiving an NSCFString.

In my case I'm mocking to pull a bundle path from file included by unit tests:

[[[_bundlePartial stub] andDo:^(NSInvocation *invocation) {
    NSString* resourceName = [invocation getArgumentAtIndexAsObject:2];
    NSString* type = [invocation getArgumentAtIndexAsObject:3];
    NSString* path = [[NSBundle bundleForClass:self.class] pathForResource:resourceName ofType:type];
    if (!path)
    {
        path = [_bundleOriginal pathForResource:resourceName ofType:type];
    }
    [invocation setReturnValue:(void*)path];
}] pathForResource:OCMOCK_ANY ofType:OCMOCK_ANY];

I call it like this:

NSString* jsonPathInBundle = [[NSBundle mainBundle] pathForResource:self.fileName ofType:self.fileExtension];

Unfortunately I'm getting back a NSCFString. This makes some sense, since my NSString is backed by a NSCFString, but when I lose the bridge I can no longer call NSString instance methods on the object. Is there a way I can return the value as an NSString?

Ben Flynn
  • 18,524
  • 20
  • 97
  • 142
  • NSString is a class cluster. *All* strings are instances of some subclass. What method can you not call on the instance? – Martin R Mar 05 '14 at 20:57
  • @MartinR It would fail on the next line: `NSString* json = [NSString stringWithContentsOfFile:jsonPathInBundle encoding:NSUTF8StringEncoding error:error];` because stringWithContentsOfFile:encoding:error: is not a method on NSCFString. – Ben Flynn Mar 05 '14 at 21:02

1 Answers1

9

After examine the OCMock code, I found my problem. Effectively a typo but its subtle enough that I don't think it's worth deleting the question.

Change:

[invocation setReturnValue:(void*)path];

To:

[invocation setReturnValue:&path];

My original way of writing this destroyed a layer of abstraction because the NSString was being treated as an address, rather than using its actual address.

Ben Flynn
  • 18,524
  • 20
  • 97
  • 142
  • 1
    Yes -- NSInvocation always needs pointers to the raw value, but you passed the value directly. It would have dereferenced the pointer, getting the "isa" value (the Class object), and treated that as an NSString instance, most likely. Secondly, if using ARC, use __unsafe_unretained on the "type" and "resourceName" variables (used to pull objects out of an NSInvocation), otherwise you will likely get crashes later on since ARC will over-release those objects. – Carl Lindberg Mar 09 '14 at 15:39