UIViewController TDD [Screencast]

January 21, 2013 — 59 Comments

This screencast focuses on the question I get the most: “Do you do test-driven development for view controllers?” It’s clearly a roadblock for many people. This screencast should remove that roadblock.

It also answers the question, “Is it worth doing?”

Outline:

  • Three types of unit test verification
  • View controller unit tests: the trick
  • TDD demo
  • How UIViewController TDD can actually help you code faster

…I did mess up something pretty big, and I don’t mean the nib and coding mistakes, which the unit tests successfully caught. I mean my failure to test the labels of the two buttons. I was so eager to show you how to test button behavior, that I forgot about how important it is to test button labels. So my apologies there, but I thought it was more important to get the information out than to try again.

Links:

See also: Testability vs. Information Hiding, a follow-up discussion about whether to expose outlets and actions in the interface.

What questions do you have after watching this screencast? Leave a comment below.

“Palette” photo by petra** (license)

Disclosure of Material Connection: Some of the links in the post above are "affiliate links." This means if you click on the link and purchase the item, I will receive an affiliate commission. Regardless, I only recommend products or services I use personally and believe will add value to my readers. I am disclosing this in accordance with the Federal Trade Commission’s 16 CFR, Part 255: "Guides Concerning the Use of Endorsements and Testimonials in Advertising."

Enjoyed this article? Sign up to get future articles by email.

Email Address:

59 responses to UIViewController TDD [Screencast]

  1. These TDD videos have been great. Thanks for your willingness to share.

    I have a question about ordering extracted functions. Do you hold to the Clean Code style of telling a story and placing higher abstraction functions first?

    It used to be harder to do this in Objective-C because of compiler limitations, but now that isn’t an issue. In your video, should updateCountLabel: go before or after the IBAction methods?

    • Zach,
      Yes, I do place the higher functions first. I just haven’t been doing that in my screencasts.

      I think Xcode’s Extract Method puts the new method above the old, because that was the safe way to do it before. Now that the compiler has relaxed the order, I wish Xcode would do what AppCode does, and put the new method below.

      So when I’m in Xcode, yes, I move them by hand.

  2. Hi Jon,
    thanks very much for this very informative and well presented tutorial.
    Could I ask, I am mid way through a development and I thought I’d try this technique but I am using a StoryBoard. Could you tell me whether there is more I need to do to set this up.
    On my first attempt the button I am testing is nil; (as I’m retrospectively applying the unit tests I’m confident the button is correctly wired up as it functions in the app.
    I think it might be because I’m using StoryBoard as opposed to nib files.

    -(void)testAcceptButtonShouldBeConnected
    {
        DetailViewController *sut = [[DetailViewController alloc] init];
        [sut view];
        assertThat([sut acceptAssignmentButton], is(notNilValue()));
    }
    • I should have done a bit more research myself before asking the above; but here is my solution :-

          NSString *mainStoryBoardName = @"MainStoryboard";
          UIStoryboard *storyboard =
              [UIStoryboard storyboardWithName:mainStoryBoardName
                                        bundle:nil];
         
          DetailViewController *sut =
              [storyboard instantiateViewControllerWithIdentifier:
              @"DetailViewController"];
         
          [sut view];
          assertThat([sut acceptAssignmentButton], is(notNilValue()));
        • This works for IBOutlets, but for some reason IBActions don’t want to test correctly when running from a Storyboard (I’ve tried with XCTest and OCMock but not Hamcrest yet):

          - (void)testPartialDatabaseRefreshButtonSelector
          {
              // OCMock:
              id mock =  [OCMockObject partialMockForObject:self.viewController];
              //testButtonPressed IBAction should be triggered
              [[mock expect] partialDatabaseRefreshButton_onTouchUpInside:[OCMArg any]];
              //simulate button press
              [self.viewController.partialDatabaseRefreshButton sendActionsForControlEvents:UIControlEventTouchUpInside];
              [mock verify];
             
              // XCTest:
              NSArray *actions = [self.viewController.partialDatabaseRefreshButton actionsForTarget:self.viewController forControlEvent:UIControlEventTouchUpInside];
              NSLog(@"%@ - %@", self.viewController.partialDatabaseRefreshButton, actions);
              XCTAssertTrue([actions containsObject:NSStringFromSelector(@selector(partialDatabaseRefreshButton_onTouchUpInside:))], @"partialDatabaseRefreshButton has no selector");
          }
  3. Hi Jon
    how can we test the actions on a UIBarButtonItem?
    Thanks

    • For UIButton, I call actionsForTarget:forControlEvent: which returns an NSArray of NSString selector names.

      But UIBarButtonItem is more like the Mac style, with a target and a single action. All we need to do is convert the action to a string:

          UIBarButtonItem *button = [sut buttonItem];
          assertThat([button target], is(sameInstance(sut)));
          assertThat(NSStringFromSelector([button action]),
                     is(@"doSomething:"));
      • Hi Jon,
        what I’m getting now is a nil returned for both of the asserts in the above code. I’m not really sure what this is telling me as the button functions.
        I think if I understand what the first assert returning nil is telling me I’ll probably understand the problem.

        • Hi
          think I know what’s happening – as this is “retro-unit testing” the view controller in question has already been instantiated in the actual application. Given that xcode runs up the app during unit testing its instantiated.
          Then in my unit test I’m create a new instance of the viewcontroller (sut) and in this one its a different button instance entirely… I think.

  4. I’m looking forward to the TDD+MVC screencast! Any expected timeline on when it’ll be put together?

    Also, I’ll be keeping an eye out for the ++count vs count++ blog post.

  5. Thanks Jon. I’m still not 100% convinced that TDDing view controllers is worth it, as mine tend not to increase much in complexity (I push everything I can out to other objects). Having said that, I take your point about the value a safety net. I’m just about to start a new smallish project — I’ll give TDDing my view controllers a try for experiment’s sake.

    • Cris, do you do something like the Humble Dialog to move out not only business logic, but also the interaction logic?

      • Maybe a bit more ad hoc/unprincipled than that (I remember coming across Feathers’ article, but not the details, so I’m not sure how close what I do is).

        Basically, I start with a plain VC, and soon as logic of any type (business or interaction) builds up, I extract it. There are some classes that typically emerge quite often from this (eg. a Selection Model class), but my overall approach is embarrassingly unsystematic.

        • There’s nothing wrong with unsystematic. :) The power of emergent design is that it’s just-in-time architecture.

          • True, but there’s a kind of cumulative wisdom in patterns, which I assume to be greater than my own native stock. We impose MVC up-front because it *is* known to work.

            I’ve just re-read the ‘The Humble Dialog Box’. What I do might end up looking similar. but I do it in the opposite order: get my VC working, then refactor logic out of it where feasible into custom objects. I suppose this is because I’ve tended to develop VCs interactively, in a compile-use-refactor cycle. A TDD approach might enable something more like Feathers’ approach (TDD-designed custom object with protocols for the UI, then implement protocols in the UI objects).

            OTOH writing VCs for iOS is so enmeshed with Apple’s code, it seems tricky to write much VC-related code against an interface/protocol, rather than the real thing. I suspect the TDD process you’ve followed in your screencast is a more natural fit with the platform.

            • It’s useful to know how to refactor towards — and sometimes, away from — patterns. Hence the book Refactoring to Patterns.

              Refactor — without unit tests? …It’s not impossible with manual testing. But it’s not repeatable, either. For me, supporting refactoring is the primary benefit of unit tests.

              • I agree. I was describing, not defending, my current practise. I have a nice little project on now that it shouldn’t be too risky trying a more all-in TDD approach with. Thanks for providing some inspiration and ideas with the screencast.

  6. Alexey Demedeckiy January 24, 2013 at 4:49 am

    What about some table view controllers testing? Static screen is simple and test looks to be clear, but more complicated example from real life are requested)

  7. The biggest disconnect I have with the state verification testing is how do I examine the state of an object without making many of the accessors public to the rest of my code. Is best practice to use categories and private properties, something else?

    • In a purely technical sense, KVC skirts the problem, allowing tests (like any other code) to access anything they want. You don’t need to make properties public just for testing.

      But that’s not to say it’s good practise to reach beyond a class’s contract to check internals. I guess the issue is: when sending a message to an object, *something* must be guaranteed by its contract (formal or informal) to happen. It must result in some change to its own state, or in messages being sent to other objects. If it’s the former, maybe that is an argument for making the state publicly accessible (what’s the point of a contract which can’t be verified?).

      • Chris, you make a good point. It is a fine line to walk with encapsulation. Perhaps I need to widen the scope of my tests in order to make sure I’m testing the expected outcome and not just rewriting the code in the test. I’ll be interested to hear more from Jon.

        • Good discussion, you guys. Zach, I really like your statement, “Perhaps I need to widen the scope of my tests in order to make sure I’m testing the expected outcome and not just rewriting the code in the test.” It’s a really good principle to keep in mind. It can also lead the other way, to extract another class. This is the theme of my new post, Testability vs. Information Hiding.

  8. Hi Jon,

    These TDD videos have really cleared up a lot of the mysticism that it held for me.

    Looking forward to starting my new “test” project which I’m planning to use TDD all the way on.

    One question, looking at the VC tests for a UIButton. The first thing that you test is that the button outlet is connected. I have made views before with buttons that run actions but don’t have an outlet in the VC because I don’t need to access it. Is this bad practise? Should every UIElement have an outlet regardless of if it is accessed?

    Thanks very much. Looking forward to the MVC video.

    Oliver

  9. Hello,

    first of all, thank you for the video. For me is a good introduction to TDD.
    However I’m not sure I’ll apply to my real projects, because these are “trivial” tests, using my standard behaviour I would notice immediately if something is not connected, just because i usually place placeholder text on the xib (for the plusButton I would use B+1) and then in didLoad I set the localizable text to be right one. So when I run the app I’m sure that 1) outlet is connecting 2) is the right text, and tapping on it I’m also sure that call it’s IBAction, all of this w\o writing a test, and I don’t get why in the fucture I would break this basic behaviour (and so, why I would need a test for it)

    I’m pretty sure that TDD is cool, but I’m not sure that these trivial test like these worth the time to use it…However I’m waiting for the next video!.

    Thanks again!.

    • Daniele, thanks for your comment.

      My example is trivial, but it opens the door for people who were blocked from writing unit tests against their view controllers. Think beyond incrementing/decrementing, to manipulating a complex model.

      You say, “So when I run the app…” meaning manual verification. What unit tests enable is automated verification. And what that enables is refactoring — the ability to change the design of any code that is automatically verified.

      Without unit tests, the attitude towards working code is, “If it’s not broken, don’t change it.” But with unit tests, the attitude becomes, “The context has changed. So continue to change the design to be as simple as possible.”

      Also consider how many steps it takes you to reach your view for manual verification. If your view is deep down in your navigation hierarchy, it can take several steps just to reach it. Time matters. My suite of hundreds of unit tests runs in a few seconds. In the screencast, you can see how often I rerun tests — at each step. Imagine that you had a magical compiler that told you, in seconds, if your program was correct.

      I don’t have to imagine!

  10. I think to understand….thank your for the reply. Probably is just something that I need to try out, or I’ll never understand the advantages. :D

  11. Gustavo Barbosa February 8, 2013 at 8:33 am

    Man, I found your blog this week and it is really awesome. I was hoping to find something like that for a long time. Thanks for sharing your thoughts! I’d appreciate if you could write something about mocking/stubing objects (maybe using OCMock or another mock framework). Congratz!

  12. Thanks for sharing your thoughts.
    I was wondering about the UIAlertView. can you please suggest me some good way to mock UIAlertView ? How do i dismiss the UIAlertView by automatically? how do i write the unit test case to test the UIAlertViewProtocols ?

  13. Hi Jon,

    How would you instantiate a view controller for TDD if the “xib” is actually a storyboard file and the layout and outlets are added to the storyboard?

    Thanks for the excellent tutorials. Finally seeing a benefit from using TDD.

  14. Hi Jon,
    Each week I conduct an iOS unit testing brown bag training session for the iOS engineers here at Mutual Mobile. This week about a dozen of us met to watch this video together, and it was very well received. We’re looking forward to watching part 2 next week.
    Thank you very much for putting this together and sharing it with us. You rock!

  15. Jon, I can’t thank you enough for putting together these videos AND some of the frameworks that help facilitate effective testing for Objective-C projects. I think the biggest impediment to high quality code on a lot of the iOS projects I’ve been involved with is lack of knowledge around good coding and testing practices, and I find myself referring more and more people here to Quality Coding.

    When you talked about the value of testing “UI fiddly stuff” in this screencast I thought I was listening to a recording of myself. :-)

    • Tim, what’s funny is that these principles aren’t necessarily challenging in an iOS context. I’m puzzled by the lack of familiarity with principles that have been around for over a decade now, among iOS developers in particular. …But that’s what we’re here to change! :)

  16. Hi Jon,

    Thanks for making these videos, much appreciated. Recently I started using Kiwi to do some Unit Testing, but didn’t really understand the methodology required for true TDD until I found these vids.

    I have a small project that I’m working on now that I’m trying out full TDD with, however I’m having a little problem. I’m trying to write a test to ensure that a UISwipeGestureRecognizer has been properly initialized with the action method I want. The trouble is that unlike UIControl there is no API that exposes the target-action pairs that have been set up for a given Gesture Recognizer.

    Any ideas how I might be able to verify this?

  17. Hi, thank you so much for this tutorial. The fact that [vc view] loads the view was a big surprise to me! One question, you have a specific reason to not use dot-syntax? Say like label.text instead of [label setText:]. Another thing, how to test if my button action presented a specific view controller?

  18. John,
    OCMock with OCHamcrestIOS in XCode5 doesn’t seem to work. Hamcrest assertions aren’t recognized by test framework. The example below is failing in XCode5.

    #import
    #import

    #define HC_SHORTHAND
    #import

    //Step1: BuildPhases Link library with binaries, added dependencies add libOCMock.a file and OCHamcrestIOS.framework
    //Step 2: Build setting –> search “header search” add “”${SRCROOT}/Libraries/include”

    @interface OCTests : SenTestCase

    @end

    @implementation OCTests

    – (void)setUp
    {
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    – (void)tearDown
    {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
    }

    – (void)testExample
    {
    //STFail(@”No implementation for \”%s\””, __PRETTY_FUNCTION__);

    NSString *s = @”adb”;
    assertThat(s, is(@”adb”));
    }

    @end

    • Make sure you’re using the latest versions – OCHamcrest 3.0.1 and OCMockito 1.1.0. If you encounter problems, it would be better to file an issue on my GitHub repos than to leave a comment here.

  19. Thanks for you tutorial on TDD with UIViewControllers. I was having trouble deciding how to start unit testing them in our current project and almost decided to not mess with it.

    Now I have a small “problem” I’m hoping you (or one of your readers) can assist with.

    We are using storyboards to build the user interface of a universal app. That means we have two storyboards: one for the iPhone and one for the iPad. I had previously discovered the trick to instantiating the view controllers for the storyboards as mentioned in a previous comment, but to properly unit test we need to test both storyboards. The options as we see them are:

    1. Create two (almost) identical unit test classes, one that loads the iPhone storyboard and one that loads the iPad storyboard. This is a pain because anytime we add a test we need to remember to add it twice.

    2. Load both storyboards in the setup method and write two (almost) identical tests for each item to be tested. Almost as painful as having two separate classes.

    3. Load both storyboards in the setup method and test both in each test. I don’t like this because then each test is doing more than one thing. Although I do occasionally do that, it’s for specific cases where I need to test two effects of one action.

    Any advice would be greatly appreciated!

    • Mark, think of it this way: Start with your first approach. Now change the part that provides the storyboard name into template method. Then extract a superclass and move the test methods up. In other words, use OO design for your tests. See “Form Template Method” in Refactoring to Patterns.

      You want to be careful, because it’s easy to get excited when you first see object-oriented design in tests. Test code needs to be as simple as possible, so it can be less SOLID than production code. But abstract test cases are a recurring pattern.

  20. How about making sure that view controllers pass data onto other view controllers when certain segues are activated?

  21. Is there any way, how to test View Controllers loaded from storyboard in swift? If I load VC from storyboard, everything is ok, until VC is inherited from my custom class. Then I cant cast it and test outlets connection …

Leave a Reply

*

Text formatting is available via select HTML.

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> 

Have you Subscribed yet? Don't miss a post!