5
votes

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?

1
NSString is a class cluster. All strings are instances of some subclass. What method can you not call on the instance?Martin R
@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

1 Answers

9
votes

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.