Unit testing with Xcode

Unit tests are low level tests written to test individual functions in code. Apple introduced XCTest with Xcode 5 in 2013 and has continually evolved it over time. This article adds unit tests to the FizzBuzz app.

Unit Testing is a form of automated testing on low level code functions. The unit tests are usually written by the developer as the code is written and the tests are run when the code is compiled. Test Driven Development or (TDD) is a software development process where the unit tests are written before the production code. It is more common to write the unit tests after the code is written.



Adding Unit Tests to an iOS App

It is relatively straight forward to add unit tests to either an existing code base or include the unit testing template when creating a new app. Xcode provides a checkbox to "Include tests" when choosing options for the project. To add unit tests to an existing project, navigate to the Test Navigator, select the + button and select "New Unit Test Target...". Then fill in the modal dialog that is presented. The specific project may need to be selected if the solution contains more than one project.

Check box to include Unit Tests when creating a new project

Check box to include Unit Tests when creating a new project

Screen to fill in when adding a new Unit Test target to a project

Screen to fill in when adding a new Unit Test target to a project



Unit Testing template

A Unit Testing template is created regardless of the mechanism of adding unit tests to the project. This template provides a placeholder for test setup and teardown as well as two empty unit tests. The first test is a regular unit test and the second one is a performance test. These tests can be run as is, and they should pass. This can be good to do to ensure that the unit test target is hooked into the project and working. Note that it might also take a little longer for unit tests to run the first time as the simulator is launched and the app runs in the background. Subsequent runs of the unit tests will run much faster.

There are multiple ways of running unit tests in Xcode. The play button in the Test Navigator can be used to run all the tests or just a single test. Right-click on a test in the Test Navigator and select run test. There is also a diamond shape in the line numbers in the XCTest file, which can be selected to run the test.

 1import XCTest
 2@testable import FizzBuzz
 3
 4class FizzBuzzTests: XCTestCase {
 5
 6    override func setUpWithError() throws {
 7        // Put setup code here. This method is called before the invocation of each test method in the class.
 8    }
 9
10    override func tearDownWithError() throws {
11        // Put teardown code here. This method is called after the invocation of each test method in the class.
12    }
13
14    func testExample() throws {
15        // This is an example of a functional test case.
16        // Use XCTAssert and related functions to verify your tests produce the correct results.
17    }
18
19    func testPerformanceExample() throws {
20        // This is an example of a performance test case.
21        measure {
22            // Put the code you want to measure the time of here.
23        }
24    }
25
26}

xcode-unit-test-template
Xcode Unit Test template



Add a single Unit Test

Delete all of the template functions and add a test to instantiate an instance of FizzBuzzModel and verify the text value is equal to "1". It can be controversial as to whether or not there is any value in such a test, but there is limited testing that can be done on the FizzBuzz model.

 1import XCTest
 2@testable import FizzBuzz
 3
 4class FizzBuzzTests: XCTestCase {
 5
 6    func test_initialValue_IsOne() {
 7        // Arrange
 8        let fizzBuzz = FizzBuzzModel()
 9
10        // Act
11
12        // Assert
13        XCTAssertEqual(fizzBuzz.text, "1")
14    }
15
16}

Single unit test to validate initial text value
Single unit test to validate initial text value



Test the Increment function

There is only one function in the FizzBuzzModel to increment the internal value. This unit test follows more of the traditional implementation of a Unit Test consisting of three steps "Arrange", "Act", and "Assert".

 1func testIncrement_FromStart_IsTwo() {
 2    // Arrange
 3    var fizzBuzz = FizzBuzzModel()
 4
 5    // Act
 6    fizzBuzz.increment()
 7
 8    // Assert
 9    XCTAssertEqual(fizzBuzz.text, "2")
10}

Results of two Unit Tests
Results of two Unit Tests



Failing Test

Let's change the expected result to an incorrect value such as "25" to see what happens when a test fails. This test fails, which results in the overall test suite failing. The error explains why the test failed ("2") is not equal to ("25"). It is also possible to set a breakpoint in the Unit Test and step through the test to see the values. It may be a code smell if a unit test is complicated that you need to debug into it to figure out what is going on.

 1func testIncrement_FromStart_IsTwo() {
 2    // Arrange
 3    var fizzBuzz = FizzBuzzModel()
 4
 5    // Act
 6    fizzBuzz.increment()
 7
 8    // Assert
 9    XCTAssertEqual(fizzBuzz.text, "25")
10}

Example of a failing Unit Test in Xcode
Example of a failing Unit Test in Xcode



Multiple Functional Tests

Here are all the Unit Tests for the FizzBuzzModel.

 1class FizzBuzzTests: XCTestCase {
 2
 3    func test_initialValue_IsOne() {
 4        // Arrange
 5        let fizzBuzz = FizzBuzzModel()
 6
 7        // Act
 8
 9        // Assert
10        XCTAssertEqual(fizzBuzz.text, "1")
11    }
12
13    func testIncrement_FromStart_IsTwo() {
14        // Arrange
15        var fizzBuzz = FizzBuzzModel()
16
17        // Act
18        fizzBuzz.increment()
19
20        // Assert
21        XCTAssertEqual(fizzBuzz.text, "2")
22    }
23
24    func testIncrement_toThree_IsFizz() {
25        // Arrange
26        var fizzBuzz = FizzBuzzModel()
27
28        // Act
29        fizzBuzz.increment()
30        fizzBuzz.increment()
31
32        // Assert
33        XCTAssertEqual(fizzBuzz.text, "Fizz")
34    }
35
36    func testIncrement_toFive_IsBuzz() {
37        // Arrange
38        var fizzBuzz = FizzBuzzModel()
39
40        // Act
41        for _ in (1..<5) {
42            fizzBuzz.increment()
43        }
44
45        // Assert
46        XCTAssertEqual(fizzBuzz.text, "Buzz")
47    }
48
49    func testIncrement_toThirty_IsFizzBuzz() {
50        // Arrange
51        var fizzBuzz = FizzBuzzModel()
52
53        // Act
54        for _ in (1..<30) {
55            fizzBuzz.increment()
56        }
57
58        // Assert
59        XCTAssertEqual(fizzBuzz.text, "FizzBuzz")
60    }
61
62}

All Unit Tests passing for FizzBuzzTests
All Unit Tests passing for FizzBuzzTests




Conclusion

Unit tests are low level tests written to test individual functions in code. Xcode provides XCTest framework to easily add Unit Testing to a project, either at the beginning of a project or later by adding a test target. This article added Unit Tests to the FizzBuzz app implemented in MVVM in SwiftUI. One of the purposes of MVVM is to separate the application logic into the model to allow testing on the model.