Swift: Unit Testing Tips and Tricks

The very first obstacle to writing Unit Tests in Swift is access controls! Since the Test Target is different from you Application Target, the files from your application, which are internal to your application target, are not available to your test target.

Fighting Access Controls

There are two ways to make your Swift application classes available to the test target:

1. Make your application classes public – this includes every variable, constant, and function you want to test

2. Add your application Swift file to your Test target. This is pretty simple to do. Just open the Utilities pane and check your application’s Test Target Membership checkbox. Thanks Andrew Bancroft for the tip!

Quote_swift

I personally prefer adding my file to the test application target (option #2). My guess is that Apple will fix this issue eventually – by allowing the test target to access the application target, so it seems much easier and safer to add my files to the test target than exposing my functions and variables publicly and eventually having to delete all the public keywords once Apple fixes the test target issue.

The first option of making your functions and variables public would work better if you’re building a framework (which has to have a public interface).

Initializing Objects

The next confusing issue is initializing objects to test in your Swift unit test, since there is no init method you can override in XCTestCase to set up all your constants and variables.

I’m going to use my Seinfeld Quotes app as an example. Let’s say I want to test my Quote object.

I have a class method that generates all my Quotes, and returns an array of Quote objects. I can create an array of Quote objects variable in my tests, and that variable will be re-calculated for every single test that is being run!

Here is the testing I used to make sure this was the case:

Screen Shot 2014-08-27 at 7.48.09 AM

Tests are run in alphabetical order. The cool thing is that removing all the quotes in testAA (which is run first), does not impact the count of Quotes in test AB, and adding an additional quote in testAB has no impact the number of Quotes counted in testAC!

This also works great for constants with variable properties:

QuoteTests_swift

So just go ahead an set up your test objects at the top of your test file. They will be re-configured in between tests!

Note: I’ll be updating this section to make it clearer very soon. Meanwhile, take a look (and join) our discussion on Twitter here.

Unwrapping Optionals

UPDATE: Here is a better way to test optionals from Apple.

Let’s say your object has a few optional variables that need testing – in my case I made my Quote object variables content and scenario optional. While unwrapping optionals is definitely the way to go in the rest of your application, I started to force unwrap them for testing.

The reason is that I want my tests to run regardless of whether the optional value is there or not:

QuoteTests_swift

In the above scenario, I want my test to fail if the quote content is not set when I explicitly set it in my test! So force unwrapping the optional is the way to go with testing optional values in my opinion:

Have you tried unit testing in Swift yet? Let me know if you have any tips or tricks in the comments!

 

Enjoy the article? Join over 14,500+ Swift developers and enthusiasts who get my weekly updates.

  • Romans Karpelcevs

    For me explicit unwrapping of all optionals didn’t work as well, tests reported some strange results and sometimes crashed.
    In the end I had to go with (can I do code mode in Disqus?)
    assert optional is not nil
    if let unwrapped = optional {
    assert unwrapped has correct value
    }

    • Make sure you use XCTAssertNotNil, not the new Swift assert!

      • Romans Karpelcevs

        Of source. I didn’t write exact code because I’m using Nimble and Quick – amazing frameworks for Swift BDD-style tests. They’re like Specta/Expecta for Obj-C, but surprisingly more stable.

    • Also, if you have a gist for your code, I can take a look.

  • I created Authorization and PasswordReset models using TDD this week. I also found Bancroft’s recommendation useful.

    What I’ve found:
    * “XCT” is hard to type
    * Using Swift’s extensions is useful to stub out methods
    * The following convention for method names helps with determining behavior “test__

    Example:
    * testAuthorize_success_isTrue
    * testDateRange_success_isWithinThreeDays
    * testFoo_failure_isBar

    CamelCase and typing “XCT” is killing me and I’ll likely switch over to using an expectation library such as https://github.com/Quick/Quick.

    I’m new to iOS and Swift as well, but I’ve found that forcing myself to use TDD/BDD is helping me to learn much more Swiftly (thanks, folks. I’ll be here all week. remember to tip your waitress).

    • Tomasz Wójcik

      Hey

      Since it’s around a year since your last post I have a question:
      Is Quick (with Nimble) self-sufficient for testing iOS apps?

      I’m new to TDD/BDD on iOS, but a huge fan of RSpec.
      Also, if you have any materials (in Swift) about testing I will be very grateful if you can drop a link to book/post/anything else.

      Thanks

      • I’ve been using Quick and Nimble for the past year, and love it. The only issue is that Xcode is not setup up to run the tests individually very well (since Quick dynamically generates the tests). One of my co-workers prefers to use XCTest, but with Nimble matchers to avoid this issue.

        • Here is a blog post I wrote on using Quick: http://natashatherobot.com/unit-testing-in-swift-a-quick-look-at-quick/. Also, this guy is the testing expert: http://qualitycoding.org/.

          • Tomasz Wójcik

            Thanks a lot for links!

            However after installing Quick via CocoaPods I still get “No such module” error…

            Did I forgot something, like linking manually Quick to my targets? (Thought that CocoaPods doesn’t require that).

          • Do you have the latest version of CocoaPods? You need the version that includes Swift support.

          • Tomasz Wójcik

            I have the 0.38.0 version.

          • Sounds right. Maybe your framework search paths are of? Here is a project I’m currently working on with Quick and Nimble installed: https://github.com/NatashaTheRobot/FitHabit

          • Tomasz Wójcik

            Strange, my build paths are empty after generating the project and running `pod install`. For the test purpose I used the Podfile from your project.

            I have tried copying variables for paths from your project but with no success :/

            Screenshot of unchanged runpath search paths and empty framework paths.
            http://i.imgur.com/3nAWxkC.png

            Do I need to add something manually after generating the project?

          • I’d recommend making sure you have the latest version of CocoaPods installed and try doing this with a brand new project – might be something specific in your current project. Also, make sure the Header Search Paths are correct as well.

          • Tomasz Wójcik

            Found the solution: https://github.com/Quick/Quick/issues/353

            Thanks for your time Natasha!

  • Frederick C. Lee

    Does anyone know how to link up parse.com into a test class?
    I can access parse.com via the bridge header for my main Swift target.
    But the test module is ignorant of this.

  • Ryan Sweeney

    Thanks for sharing!
    Do you know if there is a way to prevent the project from building every time you run tests? In the TDD form of Red, Green, Refactor, it is not my test that initially fails, but my build. I found an area where the build could be disabled on tests in “Edit Scheme” but the checkbox is disabled. 🙁

    • matthewflint

      “Test Without Building ^⌘U”

  • Arjun Kalidas

    Hi, I am not able to see the “Play” button next to the function in Swift test files. This occurs when I create a new function. Existing ones have the “Play” button in place. Any idea, why this is happening?
    Thanks in advance!