Labs ICT
Pro Login

Testing in Swift

Unit tests and UI tests.

XCTest Framework

XCTest is Apple's built-in testing framework. It comes with Xcode and lets you write unit tests, performance tests, and UI tests. Tests run automatically and give you clear pass/fail results. Good tests catch bugs before your users do.

import XCTest
@testable import MyApp

final class CalculatorTests: XCTestCase {
    func testAddition() {
        let calc = Calculator()
        XCTAssertEqual(calc.add(2, 3), 5)
    }
}
Try it Yourself ->

Unit Tests with XCTestCase

Each test is a method that starts with test. Create an instance of your class, call methods, and verify results. XCTest reports which tests pass and which fail.

final class MathTests: XCTestCase {
    func testMultiply() {
        let result = Math.multiply(4, 5)
        XCTAssertEqual(result, 20)
    }

    func testDivide() {
        let result = Math.divide(10, by: 2)
        XCTAssertEqual(result, 5)
    }

    func testDivideByZero() {
        XCTAssertThrowsError(Math.divide(10, by: 0)) { error in
            XCTAssertEqual(error as? MathError, .divisionByZero)
        }
    }
}
Try it Yourself ->

XCTAssert Functions

XCTest gives you many assertion functions. Use the right one for each check. More specific assertions give better failure messages, making it easier to find and fix problems.

func testExamples() {
    // Equality
    XCTAssertEqual(a, b, "Values should be equal")

    // Boolean
    XCTAssertTrue(isValid)
    XCTAssertFalse(isEmpty)

    // Nil checks
    XCTAssertNotNil(result)
    XCTAssertNil(error)

    // Identical
    XCTAssertTrue(obj1 === obj2)

    // Throwing
    XCTAssertNoThrow(try riskyOperation())
}
Try it Yourself ->

setUp and tearDown

Use setUp to create shared resources before each test. Use tearDown to clean up after. This keeps tests isolated — one test's state won't leak into another.

final class DatabaseTests: XCTestCase {
    var database: Database!

    override func setUp() {
        super.setUp()
        database = Database.inMemory()
    }

    override func tearDown() {
        database = nil
        super.tearDown()
    }

    func testInsert() {
        database.insert(name: "Alice")
        XCTAssertEqual(database.count, 1)
    }

    func testDelete() {
        database.insert(name: "Bob")
        database.delete(name: "Bob")
        XCTAssertEqual(database.count, 0)
    }
}
Try it Yourself ->

UI Tests with XCUITest

UI tests simulate real user interaction — tapping buttons, typing text, scrolling. They verify that your interface works end to end, not just that individual functions return correct values.

import XCTest

final class LoginUITests: XCTestCase {
    let app = XCUIApplication()

    override func setUp() {
        continueAfterFailure = false
        app.launch()
    }

    func testLoginFlow() {
        let emailField = app.textFields["email"]
        let passwordField = app.textFields["password"]
        let loginButton = app.buttons["Login"]

        emailField.tap()
        emailField.typeText("user@example.com")

        passwordField.tap()
        passwordField.typeText("secret123")

        loginButton.tap()

        XCTAssertTrue(app.staticTexts["Welcome!"].waitForExistence(timeout: 5))
    }
}
Try it Yourself ->

Testing Async Code

Test async functions with XCTExpectFailure or use async test methods. XCTest supports async/await so your tests can call async functions directly with await.

final class NetworkTests: XCTestCase {
    func testFetchUser() async throws {
        let user = try await UserService.fetchUser(id: 1)
        XCTAssertEqual(user.name, "Alice")
    }

    func testFetchFailsWithBadID() async {
        do {
            _ = try await UserService.fetchUser(id: -1)
            XCTFail("Should have thrown an error")
        } catch {
            XCTAssertTrue(error is NetworkError)
        }
    }
}
Try it Yourself ->

Code Coverage

Xcode tracks which lines of your code tests actually run. High coverage means fewer untested paths. Enable it in your scheme settings and check the report after running tests.

// Run tests with coverage in Xcode:
// 1. Edit Scheme -> Test -> Options -> Code Coverage
// 2. Enable "Code Coverage" for your target
// 3. Run tests (Cmd+U)
// 4. Check Report Navigator for coverage details
//
// Aim for 70-80%+ coverage on critical paths
// Don't chase 100% — test what matters
Try it Yourself ->