XCTest: Mocking, UI Tests & CI
Dependency Injection for Testability
protocol NetworkClient {
func fetchUser(id: Int, completion: @escaping (User?) -> Void)
}
// Receives its dependency via the initializer -- a test can substitute
// a mock, unlike a class that creates URLSession.shared internally
class UserService {
let client: NetworkClient
init(client: NetworkClient) { self.client = client }
}
class MockNetworkClient: NetworkClient {
var stubbedUser: User?
func fetchUser(id: Int, completion: @escaping (User?) -> Void) {
completion(stubbedUser)
}
}
func testUserServiceReturnsFetchedUser() {
let mockClient = MockNetworkClient()
mockClient.stubbedUser = User(id: 1, name: "Alice")
let sut = UserService(client: mockClient)
// ... exercise sut and assert, with no real network call made
}UI Tests (XCUITest)
class LoginUITests: XCTestCase {
func testLoginFlow() {
let app = XCUIApplication()
app.launch()
// Actually launches the app and taps a real, rendered button --
// verifies the full integration, not just isolated logic
app.textFields["Username"].tap()
app.textFields["Username"].typeText("alice")
app.buttons["Login"].tap()
XCTAssertTrue(app.staticTexts["Welcome, alice"].exists)
}
}Performance Testing
func testSortPerformance() {
let largeArray = (0..<10_000).shuffled()
// Runs multiple times, tracking metrics like wall-clock time --
// Xcode flags a later code change that makes this meaningfully slower
measure {
_ = largeArray.sorted()
}
}Running Tests in CI
xcodebuild test \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 15'
# Runs the same XCTest suite headlessly -- what makes automated CI
# testing (GitHub Actions, Bitrise, Xcode Cloud) possible for
# iOS/macOS projects, without the Xcode GUI openKeep your own version of these notes — editable, searchable, and organised by your stack.
Start free