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.
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.
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.
The interface for
TweetView has a method to add individual tweets to the view.
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:
- Using a real connection would make the test slow.
- We never really know what Twitter returns at any given moment.
- 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.
Introducing a stub for the unit test
Using OCMock we can create a mock object for
TwitterConnection and write the test like this:
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:
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:
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
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
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:
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:
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.