OCMOCK_ANY for values

Discussion of the OCMock framework. If you have patches we would prefer you to send them to the mailing list, but attaching them to a topic is possible, too.

OCMOCK_ANY for values

Postby peterkmoch » 08 Jun 2010, 18:01

Hi,

I'd like to be able to pass some equivalent of OCMOCK_ANY into a method that takes a value type.

@interface MyClass

- (void)MyMethod:(int)param;

@end

...

id mockMyClass = [OCMockObject mockForClass:[MyClass class]];

[[mockMyClass expect] MyMethod:OCMOCK_ANY];

The above code won't work as the mock MyMethod expects an int. I'd like to be able to tell it to accept any int. Is there a good way to do this, apart from creating a stub method?

Cheers
peterkmoch
 

Re: OCMOCK_ANY for values

Postby erik » 09 Jun 2010, 02:56

Something similar has been asked in the "anyPointer when C-Struct (ex. NSRect) is expected" thread. The problem with the current approach is that any needs to be represented as a special value that can be passed as the argument. With an object it's trivial, with a pointer it's doable because we know that some values can't possibly be pointers, but for structs and scalars it's difficult, and with scalars there could also be a size issue.

Imagine we chose -2345 as a special value to represent the any value. What happens when somebody wants to use any value with a char (size issue) or when they actually want to check that a method is called with -2345 as a value (collision issue)?

I really don't have a good idea how to solve this. Anyone?

erik
erik
 
Posts: 90
Joined: 10 Oct 2009, 15:22
Location: Hamburg, Germany

Re: OCMOCK_ANY for values

Postby peterkmoch » 09 Jun 2010, 19:48

It's quite a change but could you insist that all value arguments to mock methods are NSValue rather than the raw value? Then you could pass in an NSValue of a different type. Or could you overload the method to also take NSValue?
peterkmoch
 

Re: OCMOCK_ANY for values

Postby erik » 10 Jun 2010, 05:54

The method signatures are not defined by OCMock, they are the methods from the real classes, which means OCMock can't change the type from, say, int to NSValue. Also, in Objective-C you can't have two methods with the same name but different argument types, which means OCMock couldn't add methods that take NSValues.

One way out would be to add methods with different but similar names. For example, take a method like this:
Code: Select all
- (void)doStuff:(int)aValue

Ideally, you'd want to do this:
Code: Select all
[[mock expect] doStuff:[OCMArg any]]

That doesn't work, though. Assuming we changed OCMock so that it recognises a special prefix in method names, say "ocm_", and understands invocations of that method with an NSValue to refer to the method without the prefix and the primitive argument, then we could write the test like this:
Code: Select all
[[mock expect] ocm_doStuff:[OCMArg any]]

However, this will result in a compiler warning because that method hasn't been declared anywhere. So, we'd also have to add this declaration somewhere in our test code:
Code: Select all
@interface NSObject(DoStuffValueMethod)
- (void)ocm_doStuff:(NSValue *)aValue
@end

This would work but I think it too ugly and convoluted to add to OCMock. Opinions?
erik
 
Posts: 90
Joined: 10 Oct 2009, 15:22
Location: Hamburg, Germany

Re: OCMOCK_ANY for values

Postby peterkmoch » 24 Jun 2010, 21:17

Hi,

Yes, you're right, that's kind of ugly. This might be slightly better, but still not ideal:

[[mock expectAny] doStuff:0]

In other words, expectAny says that the parameters to the method are ignored. I guess the first problem is if you want:

[[mock expect] doStuff:[OCMArg any] withOtherStuff:1]

in other words, if you want one parameter to be any and the other to be specific.

Also, it seems like it's breaking the design pattern, that the arguments specify the behavior, in my suggestion, it's the "expectAny" that changes the behavior of the method.
peterkmoch
 

Re: OCMOCK_ANY for values

Postby chrispix » 16 Feb 2011, 00:30

I'm running into this problem more and more these days.

What if it were up to the caller to specify the special value(s) which should always match? For example:
Code: Select all
UIViewController *controller = ...
id mockController = [OCMockObject partialMockForObject:controller];

CGRect alwaysMatchCGRect = CGRectZero;
int alwaysMatchInt = 0;
[mockController alwaysMatch:OCMOCK_VALUE(alwaysMatchCGRect)];
[mockController alwaysMatch:OCMOCK_VALUE(alwaysMatchInt)];

[[mockController stub] presentPopoverFromRect:alwaysMatchCGRect inView:[OCMarg any] permittedArrowDirections:alwaysMatchInt animated:YES];


Then OCMockRecorder would keep a set of alwaysMatchers:
Code: Select all
-(void)alwaysMatch:(NSValue *)value {
   [[self alwaysMatchers] andObject:value];
}


And matchesInvocation would pass if it finds one of those values:
Code: Select all
- (BOOL)matchesInvocation:(NSInvocation *)anInvocation {

...

      if([recordedArg isKindOfClass:[NSValue class]]) {
         if ([alwaysMatchers containsObject:recordedArg]) {
            recordedArg = [OCMArg any];
         }
         recordedArg = [OCMArg resolveSpecialValues:recordedArg];         
      }
chrispix
 
Posts: 7
Joined: 07 May 2010, 19:32

Re: OCMOCK_ANY for values

Postby erik » 23 Feb 2011, 22:11

That's a new idea; not ideal, but the whole situation is not ideal. I like the direction this is going but I think it needs some refinement to make it more palatable to the user. Maybe we can create a macro that, as a side effect, does the registration of the special value? Hm...
erik
 
Posts: 90
Joined: 10 Oct 2009, 15:22
Location: Hamburg, Germany

Re: OCMOCK_ANY for values

Postby skrul » 11 Mar 2011, 18:54

I think there is still an issue with methods that have multiple same-typed primitive arguments where you want to "any match" some based on a special registered value, and exact match others on that same value. Building on chrispix's example:

int alwaysMatchInt = 0;
[mockController alwaysMatch:OCMOCK_VALUE(alwaysMatchInt)];
[[mockController stub] someMethodWithInt:alwaysMatchInt otherIntArgument:0];

The matcher would not know the difference between the first argument that matches the registered "any" value and the second argument that you want to explicitly match the value 0. You could argue that it is up to the test writer to choose a better value for alwaysMatchInt, but that seems a little brittle.

For this reason, I think any solution is going to have to include argument names rather than values. Maybe you can pass stub/expect a list of argument names that should always match:

[[mockController stubWithAny:@"otherIntArgument", nil] someMethodWithInt:0 otherIntArgument:0];
skrul
 
Posts: 3
Joined: 11 Nov 2010, 23:05

Re: OCMOCK_ANY for values

Postby dvmorris » 08 Jul 2011, 04:51

Has there been any progress on this front? I'm really stuck with some functions that I can't mock in my app because they have NSInteger parameters.

Thanks,
Dave
dvmorris
 

Re: OCMOCK_ANY for values

Postby chrispix » 16 Feb 2012, 00:33

This continues to plague me. What about allowing for selector-based matching? Something like:

Code: Select all
[mockController expect:@selector(someMethodWithInt:otherIntArgument:)];
[mockController reject:@selector(someMethodWithInt:otherIntArgument:)];
[[mockController stub:@selector(someMethodWithInt:otherIntArgument:)] andReturn:@"foo"];


Using either of these would ignore all arguments and match any invocation. It wouldn't help when it's important to match one or more arguments specifically, but I suspect it would work for 90% of cases.
chrispix
 
Posts: 7
Joined: 07 May 2010, 19:32

Next

Return to OCMock