SpringOne Americas

Private Events

Blogs

View all Blogs >>
  • Andrew Glover

    Co-author of "Continuous Integration"

    Every once in a while the topic of code coverage surfaces, which more»

  • Stuart Halloway

    CEO of Relevance

    Programmers coming to functional languages for the first time cannot imagine life without variables. I address this head-on in the more»

  • Richard Monson-Haefel

    VP of Developer Relations, Curl Inc.

    more»

  • Neal Ford

    Application Architect at ThoughtWorks, Inc.

    The lowly whiteboard is one of my favorite tools for design work on projects: you can stand in front of it as a group, you can easily play... more»

  • Michael Nygard

    Agile technology leader and dynamicist

    Sizing, Danish Style Folks in telecommunications and operations research have used Erl more»

  • Matt Raible

    Creator of AppFuse and author of Spring Live

    It's been three weeks since I joined the realm of the unemployed. Fortunately, I more»

  • Alex Miller

    Sr. Engineer with Terracotta Inc.

    Or maybe that should be “a bit of final advice”. :) There was a more»

  • Vladimir Vivien

    Software Engineer / Consultant

    I finally downloaded the latest JDK 6 u 10 (download) recently. This is a significant re more»

  • Scott Leberknight

    Chief Architect at Near Infinity

    Re nae Bair's post on The Ranting Rubyis more»

  • Graeme Rocher

    Project Lead of the Grails Project & CTO of G2One

    Those crazy guys over at the Grails podcast interviewed me about various things ranging from being part of more»

  • Ted Neward

    Enterprise, Virtual Machine and Language Wonk

    Dustin Campbell, a self-professed "IDE guy", is speaking at the .NET Developer's Association of Redmond this evening, on the future of... more»

  • Pratik Patel

    Enterprise Architect

    There's been a 'backlash' of sorts brewing in the Java developer community over the past 2 years. From talking to my developer buddies around... more»

  • Howard Lewis Ship

    Creator of Tapestry and HiveMind

    Seems like the Mac has a huge number of RSS readers. For a while I was using Vienna, but it stopped working after a recent update (no blogs... more»

  • Mike Levin

    Software Developer specializing in Web2.0 websites

    (photo from more»

  • Brian Pontarelli

    Founder of Inversoft

    Just figured out how to get git tab completion working in zsh on a Mac. Turns out that the completion scripts use a bunch of extra git... more»

  • Erik Doernenburg

    Principal Consultant @ Thoughtworks

    If you are somebody who writes code you probably know that moment when you look at some code you didn’t write, or some code you wrote a... more»

  • Kirk Knoernschild

    Software Developer & Mentor

    more»

  • Brian Goetz

    Author of Java Concurrency in Practice

    I live in an AT&T-free state, so I have not had access to the cult that is iPhone. But recently, in preparation for AT&T moving... more»

  • Matthew Bass

    Software Developer & Entrepreneur

    Can Sphinx and foxy fixtures place nicely together? Due to the way Sphinx indexing works, foxy fixtures will often slow down the indexing... more»

  • Jason Rudolph

    Author of Getting Started with Grails

    I had the more»

  • Ryan Shriver

    Business and Technology Consulting

    more»

  • Nathaniel Schutta

    Author, speaker, software engineer focused on user interface design.

    Today we learned something important, the NTSB announced the more»

  • Jeff Brown

    SpringSource Engineering And Professional Services - Groovy and Grails Developer

    Strange enough title.Let's start with a hypothetical conversation between a geeky developer and his much less geeky wife: more»

  • Jared Richardson

    Agile coach and co-author of Ship It

    Jurgen Appelo has an ongoing interview series on his blog. He's published a lot of very smart people and I'm honored to squeak in too! ;) more»

  • David Bock

    Principal Consultant, CodeSherpas Inc.

    I have been setting up a rock-solid server cluster for a client and ran into an interesting issue trying to install Phusion Passenger onto... more»

  • Pramod Sadalage

    Co-author of "Refactoring Databases:Evolutionary Database Development"

    Consider this Hibernate mapping @Column(name = "qReferenceId") public Long getQReferenceId() { return qReferenceId; more»

  • Craig Walls

    Author of Spring in Action

    At one time not too long ago, I wasn't a big fan of annotations. But then I let my guard down and even started liking them. But now I'm... more»

  • Kenneth Kousen

    President of Kousen IT, Inc.

    In this entry in my “Making Swing Groovy” series, I want to talk about threading issues. Specifically, more»

  • Venkat Subramaniam

    Founder of Agile Developer, Inc.

    I wrote a four part article for Java World on creating DSLs in Java and Groovy. For your convenience, I decided to list the links to those... more»

  • Jason Harwig

    Senior Software Engineer at Near Infinity

    The most popular entry I've written at Near Infinity has been the more»

  • John Heintz

    Principal Consultant with New Aspects of Software

    In a recent discussion interview questions came up, here's my favorite one.To set some context this question is designed to gauge the abst more»

  • Mark Johnson

    Director of Consulting at CGI

    At the Columbus NFJS show held on July 25-27th during one of the BOF sessions Dave Bock, Scott Davis and I discussed unit tests vs functional... more»

  • Joseph Nusairat

    Author of Beginning JBoss Seam & Co-Author of Beginning Groovy & Grails

    Well i am assuming Apress has the most random site in the world at times.But today only they have our recent book, Beginning Groovy & Grai more»

  • Keith Donald

    Lead of Spring Web and Creator of Spring Web Flow

    I am pleased to announce that Developing Rich Web Applications with Spring, a three-day bootcamp lead by SpringSource engineers on web... more»

  • Pete Behrens

    Organizational Agility Coach

    Marti nig & Associates Methods & Tools group recentl more»

  • Brian Sam-Bodden

    Java author, Ruby geek and Open Source Advocate

    In this installment we are going to build the Dashboard page of the Tempo application. T more»

  • Mark Fisher

    Spring Integration Lead

    In my recent post, I had mentio more»

  • Ron Bodkin

    Chief Software Architect, Quantcast

    I'm looking forward to speaking at The Rich Web Experience conference in San Jose next month. The event runs from September 7th through 9th.... more»

  • Mark Goodwin

    Web Application Security Specialist

    We've already looked at one of the two big problems posed by anti DNS pinning on Java applets; because there's rebinding on the applet and... more»

  • Scott Davis

    Author of "Groovy Recipes" & TDD Expert

    Every time I see a live show at the Denver Botanic more»

  • Romain Guy

    Java User Interface expert.

    more»

  • Ramnivas Laddad

    Author of AspectJ in Action, Principal at SpringSource

    InfoQ.com has published my AOP myths and realities talk recorded at a No Fluff Just Stuff conference. InfoQ.com founded by Floyd Marine more»

  • David Geary

    Author of Graphic Java and co-author of Core JSF

    The 2006 NFJS tour kicked off t more»

  • Kito Mann

    Editor-in-chief of JSF Central and the author of JSF in Action

    I miss the latest.integration keyword from ivy.... more»

  • Jason Hunter

    Author of Java Servlet Programming

    I just posted the JDOM 1.1 release for download. This release includes about 20 improvements and bug fixes. more»

Testing Cocoa Controllers with OCMock

Posted by: Erik Doernenburg on 07/03/2008

For a few releases the Apple development tools have included OCUnit and many developers have now started to write unit tests. There are lots of tutorials that explain how this is done for the straight-forward cases but there’s one area of testing that has proven difficult on most platforms, and that is testing of the user interface. That said, there are a few things that make this an easier problem to solve with Cocoa and in this post I’ll explain why.

Let’s look at an example. CCMenu is a small application that displays the status of CruiseControl continuous integration servers. As part of adding a new project to be monitored the user has to enter the URL for the CruiseControl server and a combox provides a history of previously used servers.

The same dialog has a matrix of buttons to set the type of the server. Actually, by default CCMenu detects the server type automatically but that’s beside the point. What we’re interested in is the piece of functionality that is responsible for selecting the correct server type for the URL chosen by the user. In our case, this would be Cruise Control Dashboard.

One difference between Cocoa and most other UI frameworks is the fact that the user interface is stored by serialising the objects, rather than generating code. For this reason I’m happy to not insist on test-first for the actual interface. What I do want to test is the code in the controller classes.

Obviously, I could load the serialised objects, locate the elements involved and then use methods such as performClick: to trigger the actions. Sounds convoluted? Yes, I agree. Luckily there’s a better way.

This is a case where testing is tricky because the object under test interacts with objects that are difficult to deal with. In such cases dynamic mock objects have proven extremely useful. A good introduction can be found here and mockobject.com lists papers and implementations. For Objective-C I created OCMock. Let’s look at how this helps us testing our controllers.

Here are the relevant parts of the interface declaration of the controller. Given that I’m doing test-driven development there’s currently no implementation of the methods. I simply created the interface of the controller based on my needs while designing the dialog.

@interface CCMPreferencesController : CCMWindowController
{
    IBOutlet NSComboBox *serverUrlComboBox;
    IBOutlet NSMatrix *serverTypeMatrix;
}

- (IBAction)historyURLSelected:(id)sender;

In my test I want to use mock objects instead of loading the actual user interface. So, in the test setup, after creating my controller, I create appropriate mock objects and set up the controller to use these.

@implementation CCMPreferencesControllerTest

- (void)setUp
{
    controller = [[[CCMPreferencesController alloc] init] autorelease];
    serverUrlComboBoxMock = [OCMockObject mockForClass:[NSComboBox class]];
    [controller setValue:serverUrlComboBoxMock forKey:@"serverUrlComboBox"];
    serverTypeMatrixMock = [OCMockObject mockForClass:[NSMatrix class]];
    [controller setValue:serverTypeMatrixMock forKey:@"serverTypeMatrix"];
}

You notice that I’m using key-value coding to set the variables (outlets). I’m doing this because the variables are not public and at the same time I don’t want to write accessor methods for them.

Now, we can start writing the actual test for the functionality. Have a look at the full test first.

- (void)testSelectsServerTypeWhenHistoryURLIsSelected
{
    NSString *selectedUrl = @"http://localhost/cctray.xml";
    [[[serverUrlComboBoxMock stub] andReturn:selectedUrl] stringValue];
    [[serverTypeMatrixMock expect] selectCellWithTag:CCMCruiseControlDashboard];
    [controller historyURLSelected:nil];
}

What’s going on here? Firstly, I tell the mock object that stands in for the actual combox box that it should stub the stringValue method. This means that when somebody invokes stringValue on this object it will return the string @”http://localhost/cctray.xml”. This is all we’d have done with the real combo box anyway.

Secondly, I tell the mock that stands in for the server type matrix that it should expect that the method selectCellWithTag: is called with CCMCruiseControlDashboard as an argument.

Lastly, I invoke the method I want to test. What happens, once the implementation is complete, is that the code will go the the combo box and ask it for its string value. The first mock will return the stubbed value. Now, the code should do whatever it needs to do to figure out which server type this corresponds to and then set that in the server type matrix by selecting the cell with the appropriate tag, and this is what we’ve told the second mock to expect.

Wait, you might say, we don’t have an implementation yet. So, how does this test fail? It doesn’t yet. We’ll have to tell the mock objects to verify that everything we told them to expect actually occurred, and a logical place for this is the test tearDown.

- (void)tearDown
{
    [serverUrlComboBoxMock verify];
    [serverTypeMatrixMock verify];
}

Strictly speaking, we don’t have to verify the combo box mock because it doesn’t have any expectations but it’s good practice to verify all mocks in the tear down, especially if the same mocks are used for multiple tests. By the way, by default the mocks also have fail-fast behaviour; when they receive a method that wasn’t stubbed or expected, they raise an exception right away. Detecting something that wasn’t expected can be done right way, detecting that something that was expected didn’t occur must be trigger by the user.

Now, if we run this test with an empty implementation of historyURLSelected: it will fail when we tell the server type matrix mock to verify because the expected method hasn’t been called. The error message will look something like this:

Unknown.m:0: error: -[CCMPreferencesControllerTest testSelectsServerTypeWhenHistoryURLIsSelected] : OCMockObject[NSMatrix]: expected method was not invoked: selectCellWithTag:0

Adding an implementation like the following one adds the right functionality and makes our test pass.

@implementation CCMPreferencesController

- (void)historyURLSelected:(id)sender
{
    NSString *serverUrl = [serverUrlComboBox stringValue];
    [serverTypeMatrix selectCellWithTag:[serverUrl cruiseControlServerType]];
}

In summary, testing controllers becomes relatively easy when we follow this pattern:

1. Replace all UI elements with mocks; using key-value coding to access the outlets.

2. Set up stubs with return values for UI elements that the controller will query.

3. Set up expectations for UI elements that the controller should manipulate.

4. Invoke the method in the controller.

5. Verify the expectations.

I find tests following this pattern easier to write and understand than tests that load a NIB file and interact with the actual user interface elements.


be the first to rate this blog


About Erik Doernenburg

Erik Doernenburg is a Principal Consultant at ThoughtWorks Inc. where he is helping clients with the design and implementation of large-scale enterprise solutions. Building on his experience with J2EE, Microsoft .NET and other environments, Erik is continually exploring patterns of enterprise software. He is an advocate of agile development and Open Source software, holds a degree in Informatics from the University of Dortmund and has studied Computer Science and Linguistics at the University College Dublin