erikdoe / ocmock

Mock objects for Objective-C
http://ocmock.org
Apache License 2.0
2.16k stars 606 forks source link

Exception raised when stubbing and returning a struct #322

Closed julia-godoy closed 7 years ago

julia-godoy commented 7 years ago

Hello, I’m having trouble when I try to stub a getter of a property that returns a struct. It seems OCMock does not allow stub method to return a struct.

I have a property called visitMock that is a class mock of CLVisit. Because of this line: OCMStub([self.visitMock coordinate]).andReturn(coordinate); I get the following exception:

Description: Return value cannot be used for method; method signature declares '{?=dd}' but value is '{CLLocationCoordinate2D=dd}'.

I’m using XCode 8 and OCMock 3.3.1. But I found it curious that the test passes with no exceptions thrown when I use the simulator on iOS 10, but the problem with the stub method appears when I use iOS 9.2.

amitfluke commented 7 years ago

I have seen similar issues in iOS 10 for iOS 9 it works fine

erikdoe commented 7 years ago

OCMock can definitely stub methods that return structs. However, there seems to have been a change in the runtime that confuses OCMock's matching code. The code to match struct names is already quite involved (see OCMEqualTypesAllowingOpaqueStructs() in OCMFunctions.m), but it looks like we need to add even more special cases here. /cc @carllindberg

It would help me if you could provide a failing test.

carllindberg commented 7 years ago

That looks like a failure in OCMEqualTypesAllowingOpaqueStructsInternal() . Those two objcType strings should compare as compatible -- though in the past it is usually "{=dd}" for unnamed structs. I thought that "?" was dealt with, but obviously not. A quick test would see if those two strings pass that function, and if not, just fix that.

julia-godoy commented 7 years ago

Hello,

Thank you for the answers.

@carllindberg I observed that when the test fails (iOS < 10.0), on OCMEqualTypesAllowingOpaqueStructsInternal(), the strings received are "{?=dd}" and "{CLLocationCoordinate2D=dd}", and the method returns NO, on this part:

/* If the names are not equal, return NO */
 if (type1NameLen != type2NameLen || strncmp(type1, type2, type1NameLen))
        return NO;

When the test passes (iOS >= 10.0), on OCMEqualTypesAllowingOpaqueStructsInternal() the strings received are "d" and "q", and the method returns YES.

@erikdoe , About tha failing test, there's the code, where visitMock is an OCMClassMock of CLVisit:

- (void)testVisited
{
    [self backgroundTest: ^{
        CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(19, 19);
        NSInteger acc = 20;
        NSDate *date = NSDate.date;
        OCMStub([self.visitMock coordinate]).andReturn(coordinate);
        OCMStub([self.visitMock horizontalAccuracy]).andReturn(acc);
        OCMStub([self.visitMock arrivalDate]).andReturn(date);

        //here there was some code that was't relevant for the crash

        [self.dataCollector visited:self.visitMock];
    }];
}

The test crashes on the first line of the "visited" method, that receives the CLVisit mocked object as a parameter:

CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:visited.coordinate
                                                                 radius:visited.horizontalAccuracy
                                                             identifier:@"clVisit"];

The program crashes when visited.coordinate is invoked, and the exception (** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Return value cannot be used for method; method signature declares '{?=dd}' but value is '{CLLocationCoordinate2D=dd}'.') is raised.

erikdoe commented 7 years ago

Thanks for the comments and additional information. I've just pushed a change that should fix the issue. Can you confirm it works for you?

carllindberg commented 7 years ago

Yeah, that should do it. We can't compare on name if one of them is "?", basically.

julia-godoy commented 7 years ago

Hello, Thank you for the fix, it worked for me!