agile-ajax

iPhone SDK: UIViewController Testing & TDD

iPhone in Dock

Unfortunately there are not enough examples out there on how to test view controllers with the iPhone SDK. My hope is to remedy that a bit by sharing some techniques I have been using to tackle the problem, particularly in keeping with the spirit of TDD along the way.

First, If you have not already done so, configure your project to use GTM. This is a bit of a no-brainer as it currently stands. Until Apple comes up with something better, GTM is the way to go. It works as advertised, and is a credit to the folks at Google for providing this toolkit.

Now then, many tutorials on the web seem to imply that one should test somewhere in the middle by suggesting to add a few outlets to your controller, fire up IB and wire up your view to your view controller before you even start to think about testing any of it.

Testing controller logic means working under the assumption that you have wired up your view components correctly to match the controller's actions and outlets. Wire up an element to the wrong action (or worse, forget to wire it up at all) and you can easily learn to rely too heavily on manual testing. While this might be acceptable for small one-off applications, within a team of developers practicing collective code ownership, this kind of error can cost real money over time. "Thanks for caring, but I'd rather have a unit test."

Now then, let's take a look at some code!

I have been taking an alternate approach to creating my views and view controllers by writing the test first and then wiring up my components to pass the test. As a result, I'm starting to see other cool ways of tackling the problem while minimizing the amount of code required to get the system under test.

The following is a short method from one of my controller tests which allows me to assert that a UIButton is properly wired up to the right action on a controller (sorry for the screenshots, I'm having problem with syntax highlighting today):

assert_selector_wired.png

The controller in the above code snippet is an instance variable referencing the controller under test. While it's possible to move this logic into a macro, I liked keeping it as a method. See "actionsForTarget:forControlEvent:" for more information on the second assertion-- this should work with any UIControl as long as you understand which UIControlEvents to accept. Here's a sample unit test which uses the helper method above:

assert_selector_wired_usage.png

This varies from other examples I have seen on the web in that I don't actually 'fire' an event through the UIButton at all. Technically that approach exercises more of the system, but in this case, if I am confident that the button is wired up to the correct action, that's good enough for me. I would rather invoke the controller logic directly within the test than make the test code longer than it needs to be.

This approach to unit testing avoids mocking, or creating custom categories to get at, say, the internals of UIEvent. Surely there is a time and place for mocks, but so far I have found the SDK to allow me just enough in most cases without requiring me to be too clever. And while I think other approaches are worthwhile in their own way, in a team environment I am less apt to mock out the internals of a system if it means making the tests harder for others to read or follow along. Clean tests are their own reward!

Let me know if you find this approach useful, or how you go about testing that your view and view controllers play nicely. I'm still looking for better ways, and plan to talk a lot more about iPhone SDK testing in the future.

Comments: 2 so far

  1. Hi,

    How were you instantiating your test UIViewController?

    vanilla alloc.init seems to not instantiate any subviews in my test code, initwithnibname doesn’t even instantiate the main view.

    Comment by mml, Monday, December 22, 2008 @ 11:26 am

  2. @mml: Thanks for the question. I seemed to have left out that important part– from your unit test, invoke ‘loadView’ on your controller immediately after ‘initWithNibName:bundle:’.

    ‘loadView’ is something you would probably never have to call from your production code (my guess is that it is invoked for you), but it initializes the view hierarchy from the nib. It’s necessary to really bring all of this under test.

    Comment by Ivan Moscoso, Monday, December 22, 2008 @ 12:23 pm

Leave a comment

Powered by WP Hashcash

Who is Pathfinder?

Topics

Search

WordPress

Comments about this site: info@pathf.com