Navigation

OCMock and the iPhone

Monday, November 10th, 2008

I recently began unit testing an iPhone app. On the desktop I rely on two tools: the excellent SenTestingKit framework built-in to Xcode and Mulle kybernetiK’s OCMock, an implementation of mock objects for Objective-C.

If you already know what mock objects are and want to skip to the good stuff, click here.

What OCMock is

Mock objects are an incredibly useful way to test software. All software programs are interconnected systems with many moving parts. To test any one part in isolation is difficult. Mock objects are the testing equivalent of cardboard cutouts. You can define procedurally how a mock object will respond to messages and verify that it received messages in a particular order, but mock objects don’t actually do anything other than return predefined values.

If that sounds a bit vague, here’s a more concrete example from OCMock’s unit test suite:

- (void)testReturnsStubbedReturnValue
{
    id returnValue;

    [[[mock stub] andReturn:@"megamock"] lowercaseString];
    returnValue = [mock lowercaseString];

    STAssertEqualObjects(@"megamock", returnValue, @"Should have returned stubbed value."); 
}

You may already see the usefulness of this technique, but in case it’s not immediately clear here’s another example:

Let’s say you’re writing parental control software and want to test the method badWordInString:. You might write something like this:

- (void)testBadWordInString
{
    id string = [OCMock mockObjectForClass:[NSString class]];
    [[[string stub] andReturn:NSRange(0, 4)] rangeOfString:[OCMConstraint any]];

    STAssertTrue([object badWordInString:string], @"Should have found bad word");
}

Granted, this is a bit of a contrived example. However, instead of testing a method that interacts with a simple string, you might be testing a method on an object that depends on the behavior of several much larger, complicated classes. Mock objects allow you to isolate methods and define all the state they need to know about in a clear, concise way.1

How to use OCMock on the iPhone

To use OCMock on the iPhone you need two things: SenTestingKit and OCMock. Neither are iPhone-ready out of the box (of course).

SenTestingKit

Currently, Apple does not provide an iPhone-compatible SenTestingKit. Luckily, the folks at Google are also testing nerds and have a solution as part of their Google Toolbox for Mac project. I didn’t even need to include the full Google Toolbox for Mac source in my project, just the 7 files mentioned in the Basic Project Setup section.

Note: The way the test framework is set up, your tests are executed at build time, NOT when you run the target. The last phase of the build process is a Run Script phase that runs a shell script which runs your tests. Test output will appear in the log pane of the Build window.

OCMock

OCMock is distributed as a framework, and the iPhone does not allow you build your own arbitrary frameworks (there are good reasons to do this on a device with only 128MB of RAM and no swap).

The solution I see in a lot of places around the web is to put OCMock.framework in /Library/Frameworks or anywhere else in the standard framework search paths. This isn’t a very good solution: your build system is now dependent upon the state of your particular machine. Not good.

Another option would be to add the source for OCMock to your unit test target. This would work but is unnecessary, as your tests will never run on the iPhone,2 so why bother building them for ARM? Executables built for the iPhone simulator, being Mac OS X binaries, can link against dynamic libraries just fine. We can use this to our advantage.

What follows is what I believe is the best way to get OCMock working with iPhone projects:

First, add OCMock.framework to your project. Make sure that it’s being added to your Unit Tests target, not your application.

Next, add a Copy Files phase to your Unit Tests target. Set it up like so:

Now, drag OCMock.framework onto your new Copy Files phase to add it to the list of files to be copied.

Finally, drag the Copy Files phase, which I renamed it to “Copy OCMock”, between the Compile Sources and Link Binary With Libraries phases.

That’s it! Run (and by run I mean build) your tests and everything should work correctly. I originally found this information in the comments of this blog article. Thanks to Mitchell Hashimoto, Michael James, and Chris Hanson for contributing to the solution.

Note: It’s important to remember that you have created a new target — you can’t just go including header files willy-nilly. The corresponding .m files for headers you include (and their dependencies) must be added to your target.

I recommend starting small. Go class by class, including one header file at a time, and get something that compiles before moving on.


  1. This, by the way, works especially well with the composition design pattern

  2. This is a limitation of the GTM iPhone Unit Test harness. Hopefully Apple will add native support for unit testing to the iPhone SDK soon. 

Comments

  1. iamthewalr.us : PHP Markdown Extra + PHP SmartyPants Typographer Text Filter for MarsEdit replied on November 11th, 2008:

    […] Extra, namely footnotes,1 which has increasingly gotten on my nerves. While writing my last post, OCMock and the iPhone, I was using footnotes enough that I decided the time to act had finally […]

  2. Matt replied on November 13th, 2008:

    Thanks for this, we were trying to do this on our own, but it didn’t work until we followed your advice and added the OC Mock framework to our project directory. Now to mock.

  3. James Moore replied on November 19th, 2008:

    One thing that I didn’t get right away is that you need to tell the “Copy OC Mock” build phase which files to copy. Just drag the OCMock.framework file onto the “Copy OC Mock” icon.

  4. Colin Barrett replied on November 21st, 2008:

    James:

    I’ve fixed that in the article. Thanks a lot!

  5. Alistair replied on December 8th, 2008:

    Also the includes for your tests are

    import <OCMock/OCMock.h>

    not

    import “OCMock.h” like the example projects

  6. Useful open source libraries for IPhone Development » Post » Coding Ventures replied on December 23rd, 2008:

    […] is also a mock object framework called OCMock that is designed for MacOS X Cocoa, but with a little bit of work you can get that to work for your IPhone […]

  7. Domain of the Bored » Blog Archive » My CocoaHeads unit testing presentation replied on January 5th, 2009:

    […] (as much as it is to any other platform); for specific information about testing on the iPhone, see Colin’s blog post. I corrected a few things that I got wrong in the original live presentation, and added a few new […]

  8. thrusty replied on January 5th, 2009:

    Great tips! One question: When you add OCMock.framework to your project, I assume you first copy the whole folder inside your project folder?

    Also, I found this confusing:

    “drag the Copy Files phase, which I renamed it to “Copy OCMock”, between the Compile Sources and Link Binary With Libraries phases”

    I eventually figured out that this meant: drag the “Copy OCMock” build phase so that it comes after “Compile Sources” and before the “Link Binary with Library”

  9. tstellanova replied on January 13th, 2009:

    A couple notes:

    • GTM can be set up to run on-device on the iPhone. So it might be interesting to build OCMock for a device target after all.

    • I had trouble including OCMock via

    import “OCMock.h”

    Instead I had to use

    import “OCMock/OCMock.h”

  10. doug replied on February 11th, 2009:

    Ok, I added OCMock.framework to my project and only to my test target. That created an entry in my Frameworks group and an entry in my test targets Link phase. I created the copy phase as you said above before the compile phase of my test target. I then setup my testBlahBlahBlah.m file to import “OCMock/OCMock.h”. It all seems to work now. The build succeeds and the tests pass that use a mock object.

    What I don’t understand is why this is all necessary. When I hover over the OCMock.framework in my Frameworks group, it shows the path is still /Library/Frameworks. Seems like my project is still then dependent on the system setup. So if I have the OCMock.framework copied into my project (somehow), why do I need to copy it into the BUILT_PRODUCTS_DIR also? I guess my bigger problem is that I don’t understand include and link paths when using XCode.

  11. Alon replied on February 13th, 2009:

    I followed similar steps to use OCMock to test for the iPhone. I found that adding Hamcrest to my testing toolkit was a significant improvement over the GTM/SenTest assertions. My experience and suggestions are at http://blog.carbonfive.com/2009/02/testing/iphone-unit-testing-toolkit.

  12. high paying affiliat replied on March 2nd, 2009:

    Great tips! One question: When you add OCMock.framework to your project, I assume you first copy the whole folder inside your project folder?