iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

XCTest

Swift testing: XCTest, Swift Testing (Swift 6+), expectations, async tests, performance tests, UI tests.

Swift — testing

EXAMPLE
// ===== XCTest (the classic) =====
import XCTest

final class MathTests: XCTestCase {
    func testAdd() {
        XCTAssertEqual(1 + 1, 2)
    }

    func testThrows() {
        XCTAssertThrowsError(try Divide(10, 0))
    }

    func testEventually() async throws {
        let value = try await fetchValue()
        XCTAssertEqual(value, 42)
    }
}

// In Xcode: Cmd+U to run.

// ===== Swift Testing (the modern framework, Swift 6+) =====
import Testing

@Test func add() {
    #expect(1 + 1 == 2)
}

@Test(arguments: [1, 2, 3])
func double(_ n: Int) {
    #expect(n * 2 == n + n)
}

@Test func userCreation() async throws {
    let user = try await User.create(name: "Alex")
    #expect(user.id != UUID())
    #expect(user.name == "Alex")
}

@Suite("Pricing")
struct PricingTests {
    @Test func emptyCartIsZero() {
        let cart = Cart()
        #expect(cart.total == 0)
    }
}

// ===== Mocks + dependency injection =====
protocol UserRepo { func find(_ id: Int) -> User? }

struct FakeRepo: UserRepo {
    var users: [Int: User]
    func find(_ id: Int) -> User? { users[id] }
}

@Test func service() {
    let repo = FakeRepo(users: [1: User(id: 1, name: "Alex")])
    let svc = UserService(repo: repo)
    #expect(svc.greet(id: 1) == "Hi Alex")
}

// ===== XCTest expectations (legacy async) =====
func testCallback() {
    let exp = expectation(description: "completion")
    fetchUser { result in
        XCTAssertNotNil(result)
        exp.fulfill()
    }
    waitForExpectations(timeout: 5)
}

// Swift Testing's async support replaces most of this.

// ===== Performance =====
final class PerfTests: XCTestCase {
    func testParseBigJSON() {
        measure {
            _ = try? JSONDecoder().decode([User].self, from: bigJSON)
        }
    }
}

// ===== UI tests (XCUITest) =====
final class UIFlowTests: XCTestCase {
    func testTapButton() {
        let app = XCUIApplication()
        app.launch()
        app.buttons["Sign in"].tap()
        XCTAssertTrue(app.textFields["Email"].waitForExistence(timeout: 3))
    }
}

// ===== Code coverage =====
// Xcode -> Edit Scheme -> Test -> Options -> Gather coverage data.

// ===== Setup + teardown =====
final class APITests: XCTestCase {
    var sut: API!
    override func setUp() async throws {
        sut = try await API.test()
    }
    override func tearDown() async throws {
        await sut.shutdown()
    }
}

// Swift Testing alternative:
@Suite struct APITests {
    let api: API
    init() async throws { api = try await API.test() }
}

// ===== Patterns =====
// - Swift Testing for new code; XCTest for legacy + UI tests
// - Inject dependencies via protocols; use fakes
// - Use #expect with descriptive messages
// - Parameterise tests with @Test(arguments: ...)

// ===== Pitfalls =====
// - Tests that hit the network -> flaky
// - Singletons in production code -> hard to test
// - Long test runtimes -> shard via test plans
// - XCUITest in CI without simulators warmed up -> slow

Why it matters

XCTest is the workhorse; Swift Testing (Swift 6+) is the modern alternative with #expect and @Test. Inject via protocols, fake in tests, parameterise, and add coverage in the scheme. UI tests via XCUITest; performance via measure. The discipline turns Swift code into a refactor-friendly codebase.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import XCTest
final class CalcTests: XCTestCase {
    func testAdd() {
        XCTAssertEqual(2 + 3, 5)
    }
}
Try it Yourself »

Discussion

Loading…