Testing with Swift and XCode 6

Over the past month I have been developing an iOS app for one of the websites that I own and operate. I wanted to share a few considerations that I encountered when investigating testing and iOS.

Objective C has been around and developing as a language for an extremely long time. Given that, there are a lot of tools out there that have been written to aid developers in testing their apps. For example: the Specta testing framework, Expecta an assertion library, and OCMock for mocking.

With the release of Swift, things have changed somewhat. Even though Swift and Objective C integrate seemlessly, I would much prefer to write all of my code in Swift. In my opinion Swift is a much cleaner language - it is easier to read and write, and feels similar to the languages I regularly use to write web apps. PHP for example.

When it comes to legacy testing tools, Swift has some problems. For example, this page outlines exactly why OCMock won't work well with Swift. That said you can still achieve everything you could possibly want/need with Swift. You might just have to approach it in a different way.

XCode 6 also throws some problems of its own into the mix. To test your codebase it needs to be accessible by your testing target. I found that the easiest way to achieve this is to toggle "Allow testing host application APIs" under the "General" section of your testing targets settings. This alone however will not work because of Swifts new access control considerations.

For a class to be accessible within the testing target, it needs to be defined as being public. This makes me cry a little inside - I really hope Apple fix this very soon. None the less.. for now.. once this is done you can import your app into your tests by simply using import AppName

Even then I encountered some intriguing issues - I could not for example instantiate a class without getting errors (Use of unresolved identifier). It seems to be the case that your testing target won't import your app target correctly if there are any issues with your app which will prevent it from building. This is somewhat annoying but it is essentially another type of test :)

My Setup

As you may well know, I am a big fan of functional testing. That is why I wrote Phantom Mochachino.

The guys over at Square developed a great functional testing tool in KIF - I highly reccomend it.

When I am testing, i like to know that things work. KIF allows me to test that my app flows how I expect it to. Some people advocate mocking out HTTP requests when using KIF, but I see no reason too. I use KIF to test that my app works end to end. If I mock out my HTTP requests I simply verify that my app works if my HTTP requests work. That in my opinion is pointless.

KIF integrates seemlessly with Swift. As outlined by Brad Heintz. All you need to do to utilize KIF with Swift syntax is create a class containing the following code

class SwiftKIFTestCase: KIFTestCase {
    func tester(_ file : String = __FILE__, _ line : Int = __LINE__) -> KIFUITestActor {
        return KIFUITestActor(inFile: file, atLine: line, delegate: self)
    }
}

You then simply extend this class when writing your test classes and call the KIF test methods on the return value of a call to tester().

One thing that I felt was lacking from KIF was the ability to reset your app. I implemented a code snippet to allow this in a similar way to which I implemented testing alternate paths in Phantom Mochachino.

To achieve this, I added the following code to my App Delegate:

    public func resApp() -> () {

        _initialStoryboard = window!.rootViewController!.storyboard;
        
        for view in self.window!.subviews {
            view.removeFromSuperview()
        }

        let initialScene:UIViewController = _initialStoryboard!.instantiateInitialViewController() as UINavigationController
        self.window!.rootViewController = initialScene;
    }

Calling this method will reset your app to the initial view controller.

I then extended KIFTestCase and added a class method to make calling this method from the testing target easy.

extension KIFTestCase {
    class func reset() -> () {
        var delegate = UIApplication.sharedApplication().delegate as AppDelegate
        delegate.resApp()
    }
}

To reset your app within a test class, you just need to call KIFTestCase.reset(). Simple.

Unit Testing

In addition to using KIF I have written some very simple unit tests with XCTest. My app is relatively simple and as such XCTest provides all the power I need to unit test my apps.

There is an extremely insightful article over at objc.io that discusses using XCTest for unit testing.

The only other consideration that I have encountered in my iOS testing journey (thus far) is mocking. Mattt has written an interesting piece on testing which outlines why mocking is a non-problem with Swift.

Conclusion

I get the impression that testing iOS apps has not always been particularly easy. Then as iOS developed things got better. Now we have Swift one may have been concerned that things could take a step backwards.

In my opinion things have not. Yes, it may take a while for there to be as much 'documentation' on testing with Swift. Likewise it may take a while until we see reliable, well used, and well maintained Swift based testing libraries. Still, I came to testing with Swift with little to no experience and i found it to be relatively painless.

Finally.. if you were worried, I do unit test my HTTP Requests.