Poking Objective-C with a Testing Stick

26 Oct 2009

![4012929364_54af446306_b](/images/2009/10/4012929364_54af446306_b-300x199.jpg
“4012929364_54af446306_b”)I’ve wandered back into
Objective-C coding land recently.
After spending so much time doing
Ruby work I’ve
gotten used to writing unit tests and
using mock objects. To that end, I
spent a bit of time figuring out OCUnit (built into XCode) and OCMock. I figured
I’d write some of this down so I don’t forget for the next time I try to set
this all up.

We’re going to work with a simple Cocoa
Application that connects to an external web resource, in this case, Google
Reader authentication. We’ll setup the testing bundle to run when our main
application is built and create a mock object so we don’t hit the external
resource on every test run. I’m going to be using XCode version 3.2 and create a
Cocoa Application called UnitTesting. If you build and run this application
you should see an empty window on screen. If you bring up the Build Results
window (Build -> Build Results) you’ll see some information about the current
build.

With our app setup,
we’ll start integrating the unit tests. Click on the Targets item in the
project folder. Select Add -> New Target. Select Unit Test Bundle and press
__Next (Note, make sure you’re in the Cocoa and not the Cocoa Touch section
when selecting Unit Test Bundle). I named my target Unit Tests and hit
Finish.

At this point if you right click on the UnitTesting target and select Build
“UnitTesting” everything should work correctly. If you right click on Unit
Tests and select Build “Unit Tests” you should receive a build failure.

Let’s add some tests. The first step
is to create a new group to hold our test files. Right click on the
UnitTesting item in the project draw and select Add -> New Group. Name the
new group Tests. Now, right click on the Tests group and select Add -> New
File…. You’ll want to add an Objective-C test case class (Note, make sure
you’re in the Cocoa Class section of the new dialog and not the Cocoa Touch
Class section or you’ll get an iPhone test class). Name the test
GReaderTest.m and de-select the Also create “GReaderTest.h” option. The
reason for this is that the headers are usually empty so there is no point in
creating the extra file. You’ll also want to make sure you have the Unit
Tests target selected.

When working with OCUnit you need to name each of your test classes
SomethingTest. The trailing Test is required. In a similar vein, each of the
tests themselves needs to start with test. So, something like
testAuthentication.

Since we didn’t create a header file we’ll need to setup the interface for the
test in the .m file.

You should be
able to build the Unit Tests target now and have the build succeed. If the
build doesn’t succeed, and you see a message about UIKit then you selected the
Cocoa Touch unit test bundle instead of the Cocoa unit test bundle.

With the unit tests
building we can add them into our main build as a dependency. Right click on the
UnitTesting target in the project drawer and select Get Info. In the General
section add a new Direct Dependency for the Unit Tests target.

Now, when you press apple-B to build the project you should see your unit tests
executed before the main build phase.

Ok, with everything setup we can start
testing. First step, each test in this set will be using our GReader object.
So, we’ll add setUp and tearDown methods that will be executed before and
after each test, respectively.

This, of course, won’t execute as we haven’t created our GReader object yet.
Let’s do that now.

Right click on the Classes group and select Add -> New File…. Add a new
Objective-C class which is a subclass of NSObject. Call this new class
GReader. The new class should be attached to both our main target and the
Unit Tests target.

Now, for our little app, the first thing we’ll need to do is authenticate with
Google Reader. There is a really good document on the Reader
API from the
pyrfeed project. We’ll need to post some
specific data to a given end point and parse the response.

For our unit tests, we don’t actually want to hit the Google endpoint. There is
too much time involved and we want our unit tests to be fast. So, we’ll need to
do some mocking in order to verify the call is happening, but not actually make
the call itself.

For this we’ll use OCMock. I’m going to add the test first and then we’ll add
the OCMock.framework into the project. First, we need to import OCMock. This is
done by adding #import <OCMock.h> to the beginning of the GReaderTest.m
file.

As you can probably tell, we’ve added two methods. There is only one test,
testAuthentication but we needed a helper function to deal with the mock post
call. Let’s take a look at what’s going on in these methods. We know that the
GReader object will be making a call that we want to mock. So, we need to
create a partial mock object that sits around our GReader object. This is done
as: id mock = [OCMockObject partialMockForObject:gr];. With the mock
created, we can stub out specific methods of the GReader object, this is called
Method Swizzling in
Objective-C land.

In order to swizzle the method we have [[[mock stub]
andCall:@selector(fakeAuthenticationPost:) onObject:self] post:[OCMArg any]];.
So, we create a new stub on our mock object. We then tell the stub to call
the fakeAuthenticationPost defined in the current object whenever the
post method is called on the GReader object. Our post method takes one
parameter so we specify [OCMArg any] to allow any argument through.

Finally, we call the authenticateWithUsername:password: on the GReader
object to kick off the authentication.

The second method we defined, fakeAuthenticationPost, will be called instead
of the post method in the GReader object. To that end, it will receive the
same parameter, the NSString that is pass to post. To be on the safe
side, I’m verifying that we’re properly passing the required email and password
fields in the string. I then fake some return data that is similar to a
successful response from Google.

If we try to build the project at this point we’re going to get a lot of errors.
First, since we haven’t added the OCMock framework and second, we haven’t
created the authenticate or post methods for our GReader object.

First things first, let’s get the OCMock framework setup. To do that, you’ll
need to download OCMock. You can grab the .dmg file off the OCMock
pages. When you extract the
archive you’ll see the OCMock.framework and the source directories. In order
to keep everything in Git, I created a Framework directory in my UnitTesting
directory. I then copied the OCMock.framework directory into this new
Framework directory.

You could also install OCMock in the /Library/Frameworks directory but I like
having it in Git as not all the developers may have OCMock installed.

Back in XCode, we need to add the framework to our project. Right click on the
Frameworks group in the project directory. Select Add -> New Group. Name the
new group Testing Frameworks. Note, this isn’t required, I just like the
separation it provides. Finally, right click on Testing Frameworks and select
Add -> Existing Frameworks... then press the Add Other… button. Navigate
to the OCMock.framework directory you just copied into Frameworks directory
and press Add. Make sure the new framework is hooked up to the Unit Tests
target. Right click on the framework and hit Get Info in the general tab you
can verify the targets.

Building the project at this point will get some warnings about missing
functions and a crash executing the unit tests. In order to fix the crash we
need to setup the build to copy the OCMock framework into our build directory.
I’m not entirely sure why this is needed, something about one of the paths set
in the framework, but it does get things working.

Right click on the Unit Tests
target and select Add -> New Build Phase -> New Copy Files Build Phase.

You need to set the Destination to Absolute Path and the Full Path to
$(BUILT_PRODUCTS_DIR). Once the Copy Files phase is created drag the
OCMock.framework from the Test Frameworks group into the copy phase. The
copy phase needs to be placed between Compile Sources and Link Binary With
Library phases.

At this point, we should
be able to execute our build again and we’ll get a failure [NSProxy
doesNotRecognizeSelector:post]. This is good. This means our mock is setup
correctly and is trying to hook into our non-existant post method.

Let’s go create a couple methods so things compile correctly. First we update
the GReader.h file as follows.

With that in place our build should succeed. You can make sure that things are
working correctly by modifying [gr authenticateWithUsername:@"dan"
password:@"password"]; to something similar to [gr
authenticateWithUsername:@"stan" password:@"password"]; and you should see a
build failure.

That’s it. We have the build system setup and the mock objects hooked up. We can
now continue down our TDD path. You can see an example project that I’ve
uploaded to GitHub.