“Imperfect tests, run frequently, are much better than perfect tests that are never written at all” — Martin Fowler

Writing testable code is a crucial skill for any iOS developer. It helps you catch bugs early, refactor safely, and build robust applications. In this article, I'll share some best practices and patterns for writing testable code in iOS.

1. Separate Concerns

Keep your business logic separate from your UI code. Use patterns like MVC, MVVM, or Clean Architecture to achieve this separation.

2. Use Dependency Injection

Inject dependencies (like network clients, data stores) instead of hardcoding them. This makes it easy to swap real implementations with mocks or stubs in tests.

class UserService {
    let apiClient: APIClient
    init(apiClient: APIClient) {
        self.apiClient = apiClient
    }
}

3. Favor Protocols Over Concrete Types

Define protocols for your dependencies. This allows you to mock them in tests.

protocol APIClient {
    func fetchData(completion: (Result) -> Void)
}

4. Minimize Global State

Global state makes tests unpredictable. Avoid singletons unless necessary, and prefer passing dependencies explicitly.

5. Write Small, Focused Functions

Small functions are easier to test and reason about. Each function should do one thing well.

6. Use Test Doubles

Use mocks, stubs, and fakes to simulate dependencies in your tests.

class MockAPIClient: APIClient {
    func fetchData(completion: (Result) -> Void) {
        // Return mock data
        completion(.success(Data()))
    }
}

7. Test ViewModels and Business Logic

Focus your tests on ViewModels and business logic, not UI code. UI tests are important but can be brittle and slow.

8. Use XCTest

Leverage XCTest for unit and UI testing in iOS. Write tests that are fast, reliable, and easy to maintain.

import XCTest
class UserServiceTests: XCTestCase {
    func testFetchUserSuccess() {
        let mockClient = MockAPIClient()
        let service = UserService(apiClient: mockClient)
        // ... test logic ...
    }
}

Conclusion

Writing testable code is an investment that pays off in the long run. By following these practices, you'll build apps that are easier to maintain, refactor, and scale.