Testing with XCTest

Testing with XCTest

As your app scales in complexity, manually clicking through every single screen to verify that a new feature didn't break an old one becomes impossible.

Apple provides the XCTest framework, natively integrated into Xcode, to automate this entire process.

Writing unit tests ensures your business logic is bulletproof and gives you the confidence to refactor code later.


The Anatomy of an XCTest

Test files are separate from your main application code. They live in a special Testing Target.

Every test suite is a class that inherits from XCTestCase. Every individual test must be a function that begins with the word test.

A Standard Unit Test:

import XCTest
@testable import MyApp // Imports your main app logic

final class MathUtilityTests: XCTestCase { func testAdditionLogic() { // The 3 pillars of testing: Arrange, Act, Assert // 1. Arrange: Set up the scenario let utility = MathUtility() // 2. Act: Perform the function you want to test let result = utility.add(5, to: 10) // 3. Assert: Verify the outcome is exactly what you expect! XCTAssertEqual(result, 15, "Adding 5 to 10 should equal 15") } }


Testing Asynchronous Code

If your code fetches data from the internet, a standard test will finish executing before the network request completes, leading to a false failure.

With modern Swift Concurrency, testing asynchronous code is miraculously easy. You just mark your test function as async throws!

Async Testing:

final class NetworkTests: XCTestCase {
    // The test function can pause and await results!
    func testUserDownload() async throws {
        let api = APIClient()
        // Wait for the network logic to finish
        let user = try await api.fetchUser()
        // Verify the parsed data is valid
        XCTAssertNotNil(user)
        XCTAssertEqual(user.name, "Akash")
    }
}

Performance Testing

XCTest also allows you to measure how long a specific block of code takes to execute.

You use the measure {} block. Xcode will run the block 10 times, calculate the standard deviation, and warn you if future code changes make the function significantly slower.

Performance Testing:

    func testSortingPerformance() {
        let largeArray = (1...100_000).shuffled()
        self.measure {
            // The compiler will meticulously time this sorting operation
            _ = largeArray.sorted()
        }
    }

Exercise

What prefix must you add to a function name inside an XCTestCase class so that Xcode recognizes it as a runnable test?