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.