XCTest is the framework that provides the foundation for writing unit tests for Swift and Xcode projects. The range of functions include test infrastructure, test case- and method definition, assertions, failure recording, performance measurement and UI interaction.


Terminology and Classification

First of all let’s clear up some terms that will eventually pop up and might cause some confusion.


XCTest

Is the name of apple’s testing framework and also the name of the abstract base class that provides shared functionality for test cases and test suites.

XCTestCase

Is used to combine multiple tests together that are being executed. Usually a XCTestCase is subclassed and the different tests are methods of the subclass.

XCTestSuite

Is a collection of XCTests. Those can either be a XCTestCase or XCTestSuite itself.

XCTestRun

Is used to collect information about the execution of a XCTest. It keeps track of the start- and end date, executed tests, failures, success, etc.


Both XCTetCase and XCTestRun are usually managed by Xcode and Swift. Therefore mostly one will not come into contact with anything other than XCTestCases but it’s good to have at least heard about the other terms.


Structure of a test case

Leaving the technicalities aside for now let’s dive into the actual basics of writing tests. Usually a test scheme consists of a number of XCTestCases which in term hold a number of singular tests. A test method is an instance method without parameters and no return value. The method name needs to begin with a lowercase test.


Consider the following example of a ShoppingCart.

struct ShoppingCart {
    private(set) var items: [String] = []

    mutating func add(_ item: String) {
        items.append(item)
    }

    mutating func remove(_ item: String) {
        items.removeAll(where: { $0 == item })
    }
}


This is a bare bone implementation of a shopping basket entity. There are the different items in the basket and methods to add/remove them. In the course of this series the implementation is going to change and grow in complexity. Alongside with it I’m going to iterate on the respective XCTestCase. To cover the status quo the following test case is going to test all of the capabilities of the current ShoppingCart.


final class ShoppingCartTests: XCTestCase {
    func testAddItem() {
        // 1
        var sut = ShoppingCart()
        let shoppingItem = "Apple"

        // 2
        sut.add(shoppingItem)

        // 3
        XCTAssertEqual(sut.items, [shoppingItem])
    }

    func testRemoveItem() {
        var sut = ShoppingCart()
        sut.add("Pear")

        sut.remove("Pear")

        XCTAssertEqual(sut.items, [])
    }
}


The test case contains two tests testAddItem and testRemoveItem.

  1. Firstly the tests preconditions are setup. An instance of the ShoppingCart (sut = System under Test) and a ShoppingItem are beining initialised.
  2. The test calls the add method that is supposed to be tested.
  3. The desired behaviour is verified by asserting if, by calling add, the provided items is added to the ShoppingCart.


testRemoveItem works in a very similar way but in order to have a meaningful test result the shopping cart should have at least one item in it.



The next post is going to break those seemingly robust tests. But don’t worry, I’m going to fix them straight away. See you next time 💙