Mocking fast enumeration

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.

Mocking fast enumeration

Postby tron_thomas » 10 Mar 2012, 07:58

I need to mock a class that supports fast enumeration. I tried something like:

Code: Select all
@implementation MyUnitTest

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state
   objects:(id*)stackbuf count:(NSUInteger)len
{
   // ...
   
    return someValue;
}

- (void)testSomeFunctionality
{
   id mock =
      [OCMockObject niceMockForClass:[ClassSupportingFastEnumeration class]];

   [(id<NSFastEnumeration>)[[mock stub]
      andCall:@selector(countByEnumeratingWithState:objects:count:)
      onObject:self]
      countByEnumeratingState:OCMOCK_ANY objects:OCMOCK_ANY count:OCMOCK_ANY];
      
   // ...
}

@end

This produces the following compiler warning when defining the stub:

Instance method '-countByEnumeratingState:object:count:' not found (return type defaults to 'id')


And the test fails at run time when defining the stub with the following error:
*** -[NSProxy doesNotRecognizeSelector:countByEnumeratingState:objects:count:] called!


What is the proper way to mock this class?
tron_thomas
 
Posts: 19
Joined: 04 Mar 2012, 02:14

Re: Mocking fast enumeration

Postby tron_thomas » 11 Mar 2012, 23:16

Since I have not received any replies yet, I'm wondering if I have not clearly explained my problem. Here is an example of the kind of test I'm trying to write:
Code: Select all
#import <SenTestingKit/SenTestingKit.h>
#import <OCMock/OCMock.h>

@interface FolderFinder : NSObject
- (NSString*)findFolder:(NSString*)folder atPath:(NSString*)path
   withFileManager:(NSFileManager*)fileManager;
@end

@interface FolderFinderTests : SenTestCase
@end

@implementation FolderFinderTests

static NSString* directoryNames[] =
{
   @"abc",
   @"def",
   @"ghi"
};

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state objects:(id*)stackbuf count:(NSUInteger)len
{
   const NSUInteger ARRAY_LENGTH = sizeof(directoryNames)/sizeof(NSString*);
    if (state->state >= ARRAY_LENGTH) {
        return 0;
    }
   
    state->itemsPtr = directoryNames;
    state->state = ARRAY_LENGTH;
    state->mutationsPtr = (unsigned long*)self;
   
    return ARRAY_LENGTH;
}

- (BOOL)fileExistsAtPath:(NSString*)path isDirectory:(BOOL*)isDirectory
{
   if ([path isEqualToString:@"/test/ghi/folder") {
      *isDirectory = YES;
      return YES;
   }
   
   return NO;
}

- (void)testFindsExistingFolder
{
   id fileManager = [OCMockObject mockForClass:[NSFileManager class]];
   id directories = [OCMockObject niceMockForClass:[NSDirectoryEnumerator class]];

   NSString* path = @"/test";
   [[[fileManager expect] andReturn:directories] enumeratorAtPath:path];
   
   [[[directories stub]
      andCall:@selector(countByEnumeratingWithState:objects:count:)
      onObject:self]
      countByEnumeratingState:OCMOCK_ANY objects:OCMOCK_ANY count:OCMOCK_ANY];
      
   [[[fileManager stub] andCall:@selector(fileExistsAtPath:isDirectory:)
      onObject:self] fileExistsAtPath:OCMOCK_ANY isDirectory:OCMOCK_ANY];
   
   NSString* expectedPath = "/test/ghi/folder";
   
   FolderFinder* FolderFinder = [[FolderFinder alloc] init];
   
   NSString* actualPath = [FolderFinder findFolder:@"folder" atPath:path
      withFileManager:fileManger];
      
   STAssertEqualObjects(expectedPath, actualPath, nil);
   
   [fileManager verify];
   [directories verify];
}

@end

Code like this produces the errors mentioned in my original post around defining the stub for the directories mock. My expectation is that the FolderFInder class should be able to use fast enumeration with the NSDirectoryEnumerator while trying to find the desired folder.

I also get a compiler warning when trying to mock fileExistsAtPath:isDirectory: because that method want's to accept a BOOL pointer, and I'm not sure how to specify which pointer it receives as the class under test will be providing that.

What is the best way for me to go about writing this test?
tron_thomas
 
Posts: 19
Joined: 04 Mar 2012, 02:14

Re: Mocking fast enumeration

Postby erik » 12 Mar 2012, 23:38

There are two different problems with the code:

The warning is caused by a typo in the method invocation. The name is countByEnumeratingWithState:objects:count: but the actual call is missing the "with".

The second problem is the use of OCMOCK_ANY. This can only be used with object arguments, not with pointers and primitives. Even for objects I'd encourage the use of [OCMArg any] instead of the macro, but for pointers it must be [OCMArg anyPointer]. For primitives there is no good solution (yet). This was discussed on the forum. For now you must pass the correct value of the value you're expecting.

The code should look like this:

Code: Select all
[[[directories stub]
    andCall:@selector(countByEnumeratingWithState:objects:count:) onObject:self]
    countByEnumeratingWithState:[OCMArg anyPointer] objects:[OCMArg anyPointer] count:16];
     
[[[fileManager stub]
    andCall:@selector(fileExistsAtPath:isDirectory:) onObject:self]
    fileExistsAtPath:[OCMArg any] isDirectory:[OCMArg anyPointer]];


By the way, is there a possibility to redefine the macro so that a compiler warning is given? I'm not ready to remove the macro but it seems to cause confusion.
erik
 
Posts: 71
Joined: 10 Oct 2009, 15:22
Location: Hamburg, Germany

Re: Mocking fast enumeration

Postby tron_thomas » 14 Mar 2012, 06:15

Thanks for your help. I was able to get the test working. It was hard to see the typo because I wasn't sure whether it was complaining about the andCall: argument or the name of the method invoked with the expectation.

I expected there was something like [OCMArg anyPointer], I just didn't know how to find it.

I was able to get the fast enumeration mock to work by hard coding the final NSUInteger argument of the callback to 16.
tron_thomas
 
Posts: 19
Joined: 04 Mar 2012, 02:14


Return to OCMock



cron