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
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
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
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
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
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
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.