Custom Constraints for OCMock

In my last post on unit testing iPhone development I introduced a couple helpful tools including OCMock, a mock objects implementation for Objective-C.

I recently came across a scenario where I needed to make an assertion on an argument passed to an expected method invocation but OCMock did not provide the constraint I needed. I was testing a FileDetailsViewController that pushes a new FileContentsViewController on to the view stack when a button is pressed. I mocked the UINavigationController so that I could add an assertion that pushViewController:animated: is called with the right arguments.

The meat of my test is:

@interface FileDetailsViewControllerTest : SenTestCase {}
    FileDetailsViewController *controller;
    id navigationController;
@end
 
@implementation FileDetailsViewControllerTest
 
-(void) setUp {
    controller = [[FileDetailsViewController alloc] initWithNibName:@"FileDetailsView" 
                                                             bundle:nil];
    navigationController = [OCMockObject mockForClass:[UINavigationController class]];
    controller.navigationController = navigationController;
}
 
-(void) testShowContentLoadsView {
    [[navigationController expect] 
         pushViewController:[OCMConstraint isKindOfClass:[FileContentsViewController class]]
                   animated:YES];
    [controller showContentsButtonWasPressed];
}
 
-(void) tearDown {
    [navigationController verify];
}
 
@end

Note that in this example, I some fanciness is required to assign controller.navigationController = navigationController since it is a readonly property in the UIViewController API. I’ll explain what I did in a later post.

The OCMock feature I was missing is the OCMock.isKindOfClass constraint. So I wrote my own.

Following is the header file that declares the isKindOfClass assertion as a static method in an Objective-C category for OCMock. Categories are kind of like Ruby mixins for Objective-C. You can add static or instance methods to an existing class definition. With this approach I can follow the OCMock pattern of providing static factory methods on the OCMock class for the range of constraints available.

//File: OCMockConstraint+Extensions.h
#import <OCMock/OCMConstraint.h>
 
@interface OCMConstraint (Extensions)
 
+ (id)isKindOfClass:(id)value;
 
@end

Following is the implementation, both of my new constraint and the factory method to create it.

//File: OCMockConstraint+Extensions.m
#import "OCMockConstraint+Extensions.h"
 
@interface OCMKindOfClassConstraint : OCMConstraint
{
    @public
    id testClass;
}
@end
 
@implementation OCMKindOfClassConstraint
 
- (BOOL)evaluate:(id)value
{
	return [value isKindOfClass:testClass];
}
 
@end
 
// Static factory method
@implementation OCMConstraint (Extensions)
 
+ (id)isKindOfClass:(id)value {
	OCMKindOfClassConstraint *constraint = [OCMKindOfClassConstraint constraint];
	constraint->testClass = value;
	return constraint;
}
 
@end

This implementation shows both how to write a custom constraint for OCMock and a use of Objective-C categories to provide a nice API for using it.

In many cases, you’ll want to write a custom constraint that is specific to your test scenario and not appropriate to be shared globally in this manner. In this case you’ll probably follow a similar strategy but implemented in your test class instead of mixed in to OCMockConstraint.

Share:
  • Digg
  • del.icio.us
  • Facebook
  • Technorati
  • StumbleUpon
  • TwitThis
  • email

3 Responses to “Custom Constraints for OCMock”


  • I’m curious about the fancy navigation controller assignment you referred to in your post.

  • Keith,

    You need to some dependency injection with the controller (assume it’s called RootController). Keep you nib file basic and create the controller programatically. In the interface add (with usual @synthesize and dealloc stuff):

    @interface RootViewController : UITableViewController
    {
    UINavigationController *navigationController;
    }
    @property (nonatomic, retain) IBOutlet UINavigationController *navigationController;

    And in the creation (e.g. the AppDelegate) add an extra line to manually set the navigation controller:

    RootViewController* rootController =
    [[RootViewController alloc] initWithNibName:@”RootViewController” bundle:nil];
    navigationController =
    [[UINavigationController alloc] initWithRootViewController:rootController];
    rootController.navigationController = navigationController;

    Then you can do the controller testing and mocking described so well above.

  • How can i run OCMock on device?

Leave a Reply