Mulle kybernetiK

presents

OCMock is an Objective-C implementation of mock objects. If you are unfamiliar with the concept of mock objects please visit mockobjects.com which has more details and discussions about this approach to testing software.

This implementation fully utilises the dynamic nature of Objective-C. It creates mock objects on the fly and uses the trampoline pattern so that you can define expectations and stubs using the same syntax that you use to call methods. No strings, no @selector, just method invocations like this:

[[myMockObject expect] doSomethingWithObject:someObject];

OCMock provides

The remainder of this page should have everything to get you started. Enjoy!

Getting ready

First steps: Using a simple Stub

The simplest use case for OCMock is the creation of stub objects, that is objects that are set up to return pre-determined values for specific method invocations.

To make this more concrete let's assume we are writing a little application that receives tweets from Twitter. We have a controller class, which for the purpose of this example we just call Controller, and another class that wraps the calls to Twitter's API, which we call TwitterConnection.

The controller has references to the connection and a view to display the tweets in. It also has a property with which the search term can be set and a method to start the process and display the tweets.

@interface Controller

@property(retain) TwitterConnection *connection;
@property(retain) TweetView *tweetView;
@property(retain) NSString *searchTerm;

- (void)displayTweets;

@end

The interface for TwitterConnection looks like this:

@interface TwitterConnection 

- (NSArray *)retrieveTweetsForSearchTerm:(NSString *)searchTerm;

@end

The method takes a search term and returns an array of Tweet objects, or nil if the request could not be handled.

Some code in Controller that uses this method looks like this:

  NSArray *tweets = [connection retrieveTweetsForSearchTerm:searchTerm];
  if (tweets != nil) {
    for (Tweet t in tweets)
      [tweetView addTweet:t];
  } else {
    /* handle error cases */
  }

If we want to test this code we could instantiate a real Twitter connection and use that. The first problem is that using a real connection would make the test slow. The bigger problem is that we never really know what Twitter returns at any given moment. And, finally, it is hard to test the error cases because Twitter usually does not return errors.

The solution to these problems is to replace the connection object with a stub. Using OCMock we can set up the test like this:

- (void)testRetrievesAndDisplaysTweets
{
  Controller *controller = [[Controller alloc] init];

  id connection = [OCMockObject mockForClass:[TwitterConnection class]];
  controller.connection = connection;

  Tweet *testTweet = /* create a tweet somehow */;   
  NSArray *tweetArray = [NSArray arrayWithObject:testTweet];
  [[[connection stub] andReturn:tweetArray] retrieveTweetsForSearchTerm:[OCMArg any]];

At this point we have a controller and a mock connection. The controller doesn't know that the connection is not a real connection but when it makes a call to the retrieveTweetsForSearchTerm: method it actually calls the mock connection, and that returns the array with the test tweet. The [OCMArg any] parameter simply tells OCMock to match all invocations of the method, no matter what argument is passed. We'll return to this later.

Next step: Verifying interactions with a mock object

In contrast to stubs, which simply provide canned answers, mocks are set up with expectations that form a specification of the calls they are expected to receive. In our case they come in handy when we want to ensure that the controller makes the calls to the view.

  id view = [OCMockObject mockForClass:[TweetView class]];
  controller.tweetView = view;

  [[view expect] addTweet:[OCMArg any]];

Now the controller has a reference to the mock view, and the mock view is set to expect an invocation of the addTweet: method.

Following this setup we can finally invoke the method under test and then tell the mock object to verify its expectations:

  [controller displayTweets];

  [view verify];

With the displayTweets imeplementation from above the test will pass. If we remove the addTweet: call then test test will fail in the mock view's verify method, which will throw an exception stating that it expected an invocation of addTweet:.

This test can be improved, though. At the moment it passes as long as the controller calls the addTweet: method. The controller could pass a wrong tweet, or even nil, and the test would still pass. To improve this we use a simple argument constraint:

  [[view expect] addTweet:testTweet];

This is the simplest argument constraint. The mock object compares whether the object that is passed when the method is invoked is equal to the object that was passed when the expectation was set up. Now the verify method will throw an exception unless the method is called with the right argument.

Apart from the any and equal constraints OCMock provides a whole set of different constraints. Stubs can also be set up not only to return values but to throw exceptions or post notifications. This is described on the features page.

Further reading

Need help?