TheJoyOfHack

For people who like to make things

Now that I’m starting a new iOS development project, I’m trying to have close-to-complete test coverage of critical parts of my code. I’m using XCTest pretty extensively, and found that I needed to test a rather complicated private method that is critical to my app’s user experience. This post shows you how I did it.

Before we get started, I know that some people feel very strongly about testing private methods. I don’t want to get into that discussion. I just know that there is one private method that I need to test thoroughly. It’s pretty simple, really.

The first thing I did was go to my project settings. I duplicated the Debug configuration by clicking on that ‘+’ and called the new configuration Unit Testing.

Build configurations
Build configurations

Then I edited my schemes and for the Test action I directed XCode to use the newly-created Unit Testing scheme:

Editing Schemes
Editing Schemes

Then I went back to my project and added a TESTING=1 preprocessor directive for the UnitTesting configuration

Adding Preprocessor Directives
Adding Preprocessor Directives

Since I use CocoaPods, I had to create a UnitTesting configuration for the Pods project as well. I didn’t need to modify schemes any further or add any preprocessor directives for CocoaPods.

With this in place, I was able to use preprocessor directives to make my private methods public (only when testing) in my .h (interface) file:

#ifdef TESTING
-(void) myPrivateMethod;
#endif

UPDATE 2014/08/12 08:34

Earlier this morning I mentioned this post to Brad Heintz, whose talk on automated unit testing I attended at the recent CocoaConf in Columbus. Brad suggested an even simpler solution, which I present here with his permission:

Declare a local category for the class under test in your test case file. Inside that category, you can re-declare the private methods so that your test case can see them. BUT: When you find yourself wanting to do that, you should step back & consider whether you’re testing behavior (which is good) or implementation.

Since the method I’m testing generates CGPoints that are fed to a UIView’s drawRect, I feel justified violating that rule of thumb this one time. It’s a lot easier to test this critical method when I have an array of points than when all I have is a UIView.