Getting started with OCMock

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.

The example

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, a class that handles the calls to the Twitter API, called TwitterConnection, and a class called TweetView that can display tweets.

The controller has references to the connection and the tweet view.

@interface Controller

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

- (void)updateTweetView;

@end

The interface for TwitterConnection has a method to retrieve new tweets. It returns an array of Tweet objects, or nil if the request could not be handled.

@interface TwitterConnection 

- (NSArray *)fetchTweets;

@end

The interface for TweetView has a method to add individual tweets to the view.

@interface TweetView 

- (void)addTweet:(Tweet *)aTweet;

@end

Why use mocks for the test

When writing a unit test for updateTweetView we have to decide what to do about the controller's dependencies, the connection and the view. In the case of the connection we could instantiate a real TwitterConnection and use that. This would lead to a few problems, though:

The solution to these problems is to replace the connection object with a stub.

Introducing a stub for the unit test

Using OCMock we can create a mock object for TwitterConnection and write the test like this:

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

  id mockConnection = OCMClassMock([TwitterConnection class]);
  controller.connection = mockConnection;

  Tweet *testTweet = /* create a tweet somehow */;   
  NSArray *tweetArray = [NSArray arrayWithObject:testTweet];
  OCMStub([mockConnection fetchTweets]).andReturn(tweetArray);

  [controller updateTweetView];
}

The test creates a controller and a mock connection. The controller doesn't know that the connection is not a real connection. The mock connection has a stub for the fetchTweets method, which is set up to return an array with a test tweet.

We can now begin to implement the updateTweetView method like this:

- (void)updateTweetView
{
  NSArray *tweets = [connection fetchTweets];
  if (tweets != nil) {
    /* display tweets */
  } else {
    /* handle error cases */
  }
}

When running the test it calls updateTweetView on the controller, which in turn calls fetchTweets on the connection. The connection is our mock, which returns the array with the test tweet. So far, so good.

Next step: Verifying interactions with a mock object

In contrast to stubs, which simply provide canned answers, mocks are used to verify interactions. In our case mocks come in handy when we want to ensure that the controller makes the calls to the view.

Expanding the unit test with a mock

Returning to the test we add a few lines to set up a mock view and to verify the controller's interaction with it:

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

  id mockConnection = OCMClassMock([TwitterConnection class]);
  controller.connection = mockConnection;

  Tweet *testTweet = /* create a tweet somehow */;   
  NSArray *tweetArray = [NSArray arrayWithObject:testTweet];
  OCMStub([mockConnection retrieveTweetsForSearchTerm:[OCMArg any]]).andReturn(tweetArray);

  id mockView = OCMClassMock([TweetView class]);
  controller.tweetView = mockView;

  [controller updateTweetView];
  
  OCMVerify([mockView addTweet:[OCMArg any]]);
}

The creation of the mock view is no different from the creation of the mock conntection above and, again, the controller doesn't know that it is connected to a mock and not a real TweetView instance.

The call to updateTweetView remains unchanged, too. After it, though, the test verifies that the addTweet: method was actually invoked, presumably as a result of the test invoking the updateTweetView method.

In the verify the test uses [OCMArg any] to tell the mock object that addTweet: can be called with any argument. It doesn't matter what tweet object is passed.

We can now extend the implementation of updateTweetView like this:

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

With this implementation the test passes. If we remove the addTweet: call from the updateTweetView method then the test will fail, reporting that an expected method was not invoked. In Xcode and AppCode the error is reported on the line of the verify.

Improving the test with argument matching

This test can be improved. As written above it passes as long as the controller invokes 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 by modifying the verification:

  OCMVerify([mockView addTweet:testTweet]);

This is the simplest argument constraint. The mock object compares whether the object that is passed in the verification is equal to the object that was passed when the method was invoked. Now the verification will fail unless the method is called with the right argument.

Apart from the any and equal constraints shown so far 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. All of this is described on the reference page.

Further reading