1️⃣Unit Testing

Spring 2024 | Vin Bui

When creating any application, it’s very important that we ensure our apps function properly and maintains that proper functionality for as long as possible. However, this can be quite difficult when working with larger codebases and more complex applications as adding new code may cause undesirable changes to our app. To maintain functionality, we often write unit tests to ensure that our code works properly after making changes to our codebase. In the iOS world, we typically write unit tests for the smallest, yet important, functions that control the logic of our app.

Getting Started

There are multiple ways to create our testing suite.

  1. If creating a brand new project, we can check the box labeled Include Tests.

  2. For an existing project, go to File > New > Target > Unit Testing Bundle. Select the project name and the target to test.

Testing File

In this chapter, we will be writing unit tests for an app called Sample. Notice that a new group gets created outside of our original project. In our case, it is called SampleTests. In this folder, we will create a new Swift file called SampleTests.swift.

In a larger, more realistic application, it’s more likely that we will have multiple screens/views. In this case, it’s common to create multiple test files, one for each screen. For example, if our Sample app contained a home page, we can have a HomeSampleTests.swift file. This keeps our codebase neat and easier to read for other developers.

Let’s analyze the code below:

import XCTest
@testable import Sample

/// Test suite for Sample.
final class SampleTests: XCTestCase {
    // Test procedures go here.
}
  1. In order to use Xcode’s testing functionality, we must import the XCTest library.

  2. We import the name of our main module, which in this case is Sample. Note that we must mark it with @testable.

  3. We create the class representing our test suite. The name of this class should match the name of our file. This class must conform to the XCTestCase protocol and is marked with final to prevent any unwanted changes to the instances of this class.

Writing Test Cases

Let’s say we wanted to test a function that counts the number of even numbers in an array. If this function is called countEvens, we can call our testing procedure testCountEvens.

func testCountEvens() {
    var result: Int
    
    // Test empty array
    result = countEvens([])
    XCTAssertEqual(result, 0)
    
    // Test array with one even number
    result = countEvens([2])
    XCTAssertEqual(result, 1)
    
    // Test array with more than one even number
    result = countEvens([2,2])
    XCTAssertEqual(result, 2)
    
    // Test array with multiple even and odd numbers
    result = countEvens([1,2,3,4,5,6,6])
    XCTAssertEqual(result, 4)
}

However, this code assumes that the Sample module that we imported has a publicly accessible function called countEvens. If this function was declared as a method inside of a class, we must first instantiate an object of that class and then call the function.

In our example, we are testing for equality so we use the XCTAssertEqual function, and pass both values as arguments.

Running the Test Procedure

There are multiple ways to run the test procedure. The most simple way is to click on the play button next to the name of the function we just created. If we want to run the entire testing suite, we can click on the play button next to the name of our testing class.

Xcode will then build our application and launch the Simulator. If our build succeeds, there will be a popup stating Build Succeeded. Note that this DOES NOT mean that our test cases have passed. If our test cases have passed, there will be a Green checkmark next to each assertion call. Otherwise, there will be a Red cross indicating a failure.

When to Write Unit Tests

The difficult part in writing unit tests is knowing when to write them. It would be OD to test every single function in our entire codebase, so it’s important that we only test functions that contain complex logic. For example, if we had a function that removes spaces from a String, that may be unnecessary because we can see that when viewing the app. However, if we had a more complex function that contains many edge cases and is difficult or impossible to view through the app, then writing unit tests can help us handle those unseen cases.

Last updated