Swift Unit Testing – Verifying Method Calls

In this unit testing screencast for Swift developers, we explore how to use Test Driven Development to verify method calls.

Getting Started Guide

If you’re new to unit testing or are trying to get set up with unit testing in a Swift project, you might check out my getting started guide before jumping into the screencast.

Screencast

Large / Full-Screen Viewing

GitHub Example

GitHub repo of the example developed in the screencast.

Screencast Transcript

[0:01]

Hi, I’m Andrew from andrewcbancroft.com, and I’m bringing you a test driven development example for iOS in Swift.

The goal of this video is to teach you how to write a unit test to verify that a method was called.

[0:21]

I’ll begin in XCode 6.1.1 with a side-by-side view of my TestCase class, and my primary View Controller class. This is what XCode generated for me when I chose to create a new Single View Application.

[0:38]

Imagine a scenario with me: Suppose that as part of your application’s requirements, you mush show an Alert View after your primary view loads. How would we go about using Test Driven Development to implement this “feature”?

[0:53]

Well, we’d write a test, of course – I’ll name it something appropriate like “testUIAlertViewShowsAfterViewLoads”

[1:04]

Next, I need to create an instance of my ViewController class so that I can test it.
But immediately, I run into trouble. It seems that my TestCase class can’t “see” my View Controller class.

[1:17]

Thankfully, it’s a simple fix: Simply add the View Controller class to your Test target.

[1:26]

With everything compiling now, we can move to the next line of test code.

[1:32]

First off, in order to test my View Controller’s Alert View functionality, the Alert View has got to be visible to my test. The easiest thing for me to do at this point is to assume that there will be a property on my View Controller that I can set. This allows me to perform a kind of dependency injection known as “setter injection”. All it really means is that the property is dual-purpose. When the app runs on my iPhone, it’ll use a real UIAlertView. But when I run it in my tests, I can plug in a UIAlertView that I control the behavior of, so that I can verify what I need to in my tests.

[2:16]

The ability to swap in a kind of Alert View that I control really is the “magic sauce” to this whole test-driven operation. In order to know whether or not a UI element was “shown” in a unit test without actually showing something on the screen in a simulator or device, I need to invent something known as a test-double. A fake object, if you will.

[2:44]

My Fake Alert View will have some special capabilities that allow me to know whether the “show()” method was called on it. At the same time, it needs to be able to be substituted in my View Controller for a real UIAlertView.

[3:00]

Since Swift supports object-oriented design, we have inheritance at our disposal here.

[3:07]

I’ll create a nested class inside my test function called FakeAlertView. Notice that it’s a subclass of UIAlertView. What’s great about this is that it meets both of my testing requirements: I can control it’s behavior, and it can be substituted anywhere a UIAlertView is needed.

[3:28]

I’ll finish fleshing out this fake object in a minute. Now that XCode isn’t complaining about not knowing what a FakeAlertView is, I’ll turn my attention to a new compiler complaint: I don’t have an alertView property on my View Controller yet, so I’ll add one.

[3:50]

There’s just a little more setup that’s needed in order to be able to verify that the show method was called. Since show() doesn’t return anything, we need some way to know that its logic was executed. I’ll do two things to expose this:

[4:06]

  1. I’ll have a boolean property called showWasCalled on my FakeAlertView that is initially set to false.
  2. I’ll override the show() method in this fake UIAlertView subclass. Inside the method body, I’ll reassign the value of showWasCalled to true. That will be enough for me to use inside an XCTAssert, which is coming up.

[4:35]

We’re nearing the finish line here. All that’s left is to call my View Controller’s viewDidLoad method, and write my assertion.

[4:44]

The only thing I’d tell you to make note of is that we need to cast the View Controller’s UIAlertView instance to a FakeAlertView so that we can access the showWasCalled property.

[4:57]

Running the test at this point should produce a failing test, which is exactly what we want (because there’s no code that calls the alertView’s show() method in viewDidLoad()).

[5:08]

The last step to this adventure is to write the production code to pass the test. In viewDidLoad, I call my alertView’s show method and re-run the tests.

[5:19]

And we’re green! Which means we’ve managed to successfully verify that a method was called.

[5:27]

Thanks for watching – I have other resources related to Swift and iOS development at andrewcbancroft.com.

  • Pingback: Swift Unit Testing Resources - Andrew Bancroft()

  • Fabio Santos

    Hey Andrew! Great video! I’m having a hard time here to get the audio right. The transcript was really helpful. Thanks! ;-)

    • Andrew Bancroft

      Thanks, Fabio! I uploaded a new version with (hopefully) louder audio. Glad the transcript helped, tho!

  • Peter Woods

    Thank you for a great screencast !

    You’re a true pioneer in this area: there are very few resources that cover unit testing in Swift
    and almost none that cover test doubles or dependency injection.

    Also, having the associated transcript and github repo is extremely helpful.
    Please keep it up !

    Just a couple of things that would make it even better:
    1. When I go full screen (which is required to see the code), the left part of the video is obscured by the sidebar
    2. The volume level is very (very) low. Any chance of increasing it ?

    • Andrew Bancroft

      Thanks a ton for the feedback! I believe I’ve got the audio situation worked out – I re-uploaded the video with the intended improvements, so hopefully version 2.0 is louder!

      Also, I did notice the crazy sidebar situation – looking into a fix for that, though I totally blame my WordPress theme for that. :] I’ll see what I can do though.

    • Andrew Bancroft

      Hey Peter,

      One more follow-up. I’m really not sure how I’ll be able to get my theme to cooperate with the full-screen view, so I added a link below the embedded YouTube area that folks can click to see a larger / full-screen view. I’m hoping this doesn’t take away from the experience too much.

      Thanks again for your comment, Peter. If you’ve got other ideas for screencasts, I’m all ears. I enjoyed making this one, and if it enhances the experience to -see- things done, rather than just read about it, I’m all for building the most helpful form of resource!

  • Pingback: TDD for iOS in Swift - What's the Goal? - Andrew Bancroft()

  • FuadKamal

    Hi Andrew, thanks for taking the time to try and do this screencast. I have to agree with the other commenters, though, the audio is lacking. I don’t mean to be hypercritical or rude, it’s just so hard to hear (for me, anyway) that I after a few seconds I found it unwatchable. If you listen carefully (or with the volume cranked to max), you can hear there is some bad feedback in the audio, on top of the issue that the levels are way to low. I would suggest re-recording the audio with a decent mic and re-posting the video, if you think it’s worth your time.

    • Andrew Bancroft

      Totally understandable – I really appreciate everyone’s feedback. I didn’t realize it was a problem until y’all point it out, so huge “thank you!” for letting me know.

      I did re-upload the video with (hopefully) louder audio. Can you maybe rewatch at least a portion and let me know if it’s an improvement?

      There is a little feedback (a little buzz sound, right?) – my mic isn’t the best. If that’s super annoying, I’ll try and re-record with a different one. My equipment for this right now is kinda sad, haha.

      • FuadKamal

        Hi Andrew, this is an improvement. But yeah, that feedback is more noticeable with the levels increased. You might be able to reduce this in post (like with a noise reduction filter) but that might introduce other artifacts as well. For the future, best bet is to invest in a good USB mic. I use and love this one, a directional mic is best for this type of recording: http://ow.ly/Gm3V7 I hear the Yeti one is very popular, as well.

        • Andrew Bancroft

          Thanks for the great tips!

          I re-recorded the audio in GarageBand, and it seems to have made a world of difference for me. It’s louder, and that high-pitched buzz noise isn’t there (for me) now. Originally I recorded it in Camtasia, so maybe it’s audio processing is sub-par…. not sure.

          In any case, I think I’ve wrapped up this round of improvements and wanted to say thanks one more time for your feedback!

  • Pingback: Swift Unit Tests [iOS developer:tips];()

  • http://darrarski.pl Dariusz Darrarski

    When initializing UIViewController using init without parameters, memory leak may occur. You should always use one of UIViewController’s documented initializers, not the one inherited from NSObject. That’s one of known Swift issues Apple didn’t solve so far, you can read more about it here: http://darrarski.pl/2014/12/not-initialize-objects-swift/

    • Andrew Bancroft

      Oh man, good catch, Dariusz!

      So I have a question for you then: Obviously this’d be a problem if I were trying to instantiate the view controller this way in my app for some reason. However, since I’m doing this in my unit test, do you have a feel for what the impact would be? I’m not noticing any negative impact on my system’s memory, no matter how many times I run the test. Now obviously my system has a ton more RAM than my phone does, but surely I’d notice something eventually, right?

      How big of a concern is it to simply call the NSObject initializer in a unit test? I appreciate any thoughts you have!

      • http://darrarski.pl Dariusz Darrarski

        I think in case of your source code, the UIAlertView will be initialized twice, then deallocated only once (when setting FakeAlertView to ViewController’s alertView property). First initialized instance will never be deinitialized.

        I am not sure if the leaks have impact on your computer when you run the app (and you do so even when you unit-test) in simulator. Perhaps simulator memory is handled in the way that allows to be cleaned up after simulator is shut down. Even if not, the amount of memory leaked in this case is so small, that it shouldn’t affect any mac computer. Obviously this would be problem on iOS device.

        You can test your code using Instruments from Xcode. Choose “Leaks” tool and check if memory leaks occur and what is leaking. My guess is UIAlertView, but check it yourself just to make sure.

        Oh, one more thing. Apparently this issue occurs not only for UIViewController, but other UIView classes too.

  • Pingback: Xcode 6: Usable Testing « Under The Bridge()

  • Almas Sapargali

    Hello, good screencast.
    Btw, I’d suggest declaring alertView property as lazy, this way, it’ll not get initialised until needed, and default UIAlertView never get’s initialised when testing. Because it’s set before lazily initialising.

  • Pingback: Swift Unit Testing - Verifying Method Calls - Andrew Bancroft - Dariusz Rybicki()

  • Pingback: Swift Unit Testing - Verifying Method Calls - Dariusz Rybicki()