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 @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.