Feature Testing with KIF in Swift

One of the things I’m currently working on is getting back into my old Rails habit of testing my code. With KIF and XCTest frameworks, testing has become a lot simpler in iOS. However, as I started with KIF in my Swift app, I ran into an issue, which was luckily easy enough to resolve. But as I’ve been writing my tests in Swift, I’ve also found some new Swift-only patterns that I’ve been enjoying and wanted to share.

I’ll use my demo Seinfeld Quotes app as an example. I’m going to simply test the adding quotes functionality. Here is how the KIF test works:

Swift KIF Setup

To set up KIF, simply follow the instructions on the KIF README. Once you’ve successfully setup KIF, make sure to add it to the Bridging Header file in your KIF Test Target:

KIF Bridging Header

To trigger the creation of the Bridging Header file, I usually just create an Objective-C Test file in my target, which I then delete after the Bridging Header file is already created.

So you might be ready to start using KIF, but unfortunately the tester is defined in KIF using #define, which doesn’t work well with Swift.

While KIF is still under work for compatibility with Swift, I added an extension, as suggested by @bnickel in the Swift KIF Issue on Github, with a slight modification to keep the tester and system syntax a lot closer to the original:

//
//  KIF+SwiftExtension.swift
//  SeinfeldQuotes
//
//  Created by Natasha Murashev on 10/5/14.
//  Copyright (c) 2014 NatashaTheRobot. All rights reserved.
//

extension XCTestCase {
    
    var tester: KIFUITestActor { return tester() }
    var system: KIFSystemTestActor { return system() }
    
    private func tester(_ file : String = __FILE__, _ line : Int = __LINE__) -> KIFUITestActor {
        return KIFUITestActor(inFile: file, atLine: line, delegate: self)
    }
    
    private func system(_ file : String = __FILE__, _ line : Int = __LINE__) -> KIFSystemTestActor {
        return KIFSystemTestActor(inFile: file, atLine: line, delegate: self)
    }
}

Accessibility Labels

KIF uses Accessibility Labels to find views on each screen. Since Accessibility Labels can change easily (e.g. if the button title changes, for example), I like to keep the string versions of all my accessibility labels as constants.

However, with Swift, I found that it could be really nice to keep these together in enums in a private extension:

// AddQuoteTests.swift

class AddQuoteTests: KIFTestCase {
    
}

// MARK: Setup Accessibility Labels

private extension AddQuoteTests {
    enum ButtonLabel: String {
        case Add = "Add"
        case Save = "Save"
    }
    
    enum NavBarTitle: String {
        case CreateQuote = "Create Quote"
        case ListQuotes = "Seinfeld Quotes"
    }
    
    enum TextFieldLabel: String {
        case QuoteContent = "Quote Content"
        case SceneDescription = "Scene Description"
    }
    
    enum TextInput: String {
        case QuoteContent = "My Awesome Quote"
        case SceneDescription = "My Scene"
    }
    
}

The Test

I like to keep my tests very readable, so I’ve found myself keeping my test very simple on first glance:

//
//  AddQuoteTests.swift
//  SeinfeldQuotes
//
//  Created by Natasha Murashev on 10/5/14.
//  Copyright (c) 2014 NatashaTheRobot. All rights reserved.
//

class AddQuoteTests: KIFTestCase {
    
    func testAddQuote() {
        tapAddButton()
        fillInQuoteData()
        saveQuote()
        assertNewQuoteCreated()
    }
    
}

Meanwhile, I like to keep all the implementation details in another private extension:

class AddQuoteTests: KIFTestCase {
    
    func testAddQuote() {
        tapAddButton()
        fillInQuoteData()
        saveQuote()
        assertNewQuoteCreated()
    }
    
}

// MARK: Test Details

private extension AddQuoteTests {
    
    func tapAddButton() {
        tester.tapViewWithAccessibilityLabel(ButtonLabel.Add.toRaw(), traits: UIAccessibilityTraitButton)
        tester.waitForViewWithAccessibilityLabel(NavBarTitle.CreateQuote.toRaw(), traits: UIAccessibilityTraitStaticText)
    }
    
    func fillInQuoteData() {
        tester.enterText(TextInput.QuoteContent.toRaw(), intoViewWithAccessibilityLabel: TextFieldLabel.QuoteContent.toRaw())
        tester.enterText(TextInput.SceneDescription.toRaw(), intoViewWithAccessibilityLabel: TextFieldLabel.SceneDescription.toRaw())
    }
    
    func saveQuote() {
        tester.tapViewWithAccessibilityLabel(ButtonLabel.Save.toRaw(), traits: UIAccessibilityTraitButton)
        tester.waitForViewWithAccessibilityLabel(NavBarTitle.ListQuotes.toRaw(), traits: UIAccessibilityTraitStaticText)
    }
    
    func assertNewQuoteCreated() {
        tester.waitForViewWithAccessibilityLabel(TextInput.QuoteContent.toRaw(), traits: UIAccessibilityTraitStaticText)
        tester.waitForViewWithAccessibilityLabel(TextInput.SceneDescription.toRaw(), traits: UIAccessibilityTraitStaticText)
    }
}

I’ll definitely be working on adding more KIF tests to my future projects! Love how readable they can be with Swift.

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