Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
119: Mocking with Protocols
I once worked with a developer who spent an entire Tuesday debugging a "flaky" test suite. Every time the CI pipeline ran, about 10% of the tests failed. It turned out the code was making actual network calls to a staging server to verify user authentication. When the staging server lagged or a network packet dropped, the tests failed. The developer was trying to fix the tests by adding sleep() calls to wait for the server, which is a nightmare—it just made the tests slower without actually fixing the instability.
The problem was that the business logic was tightly coupled to a concrete network class. To fix this, we have to stop our classes from caring how a task is done and instead make them care what can be done. In Swift, we do this through protocols.
Breaking the Concrete Link
When you instantiate a service directly inside a ViewModel or a Manager, you're creating a hard dependency. If your PaymentViewModel initializes a StripePaymentProcessor internally, you can't test that ViewModel without actually involving the Stripe SDK. That's a recipe for slow, brittle tests that require an internet connection.
Instead, we define a protocol that describes the interface. If the ViewModel only knows that it's talking to something that can process a payment, it doesn't matter if that something is a real API client or a fake object we created specifically for a test.
protocol PaymentProcessor {
func processPayment(amount: Double, completion: @escaping (Bool) -> Void)
}
class StripePaymentProcessor: PaymentProcessor {
func processPayment(amount: Double, completion: @escaping (Bool) -> Void) {
// Actual complex network logic here
}
}
class PaymentViewModel {
private let processor: PaymentProcessor
// We "inject" the dependency through the initializer
init(processor: PaymentProcessor) {
self.processor = processor
}
func pay(amount: Double) {
processor.processPayment(amount: amount) { success in
print(success ? "Paid!" : "Failed!")
}
}
}
Swapping Reality for Control
Now that the PaymentViewModel depends on the PaymentProcessor protocol rather than the StripePaymentProcessor class, we can create a "Mock." A mock is just a lightweight version of the service where we control the output. I usually add properties to my mocks to track whether a method was called or to force a specific return value.
This is where the magic happens. In your production code, you pass in the real Stripe service. In your test target, you pass in the mock. You no longer need to worry about API keys, network timeouts, or hitting rate limits during a test run.
class MockPaymentProcessor: PaymentProcessor {
var processPaymentCalled = false
var shouldSucceed = true
func processPayment(amount: Double, completion: @escaping (Bool) -> Void) {
processPaymentCalled = true
completion(shouldSucceed)
}
}
// In your test:
let mock = MockPaymentProcessor()
mock.shouldSucceed = false // Force a failure scenario
let viewModel = PaymentViewModel(processor: mock)
viewModel.pay(amount: 10.0)
assert(mock.processPaymentCalled == true)
I've found that this pattern—Dependency Injection via Protocols—is the single most effective way to reduce the "fear" of refactoring. When you have a suite of mocks, you can test every single edge case (like a 404 error or an empty JSON response) in milliseconds, rather than trying to manually trigger those errors on a real server.
📋 Practical Task
Exercise: Building a Mockable Weather Service
You are tasked with testing a WeatherDashboard class that displays the current temperature. Currently, it is tightly coupled to a RealWeatherAPI class that makes actual HTTP requests.
Your Goal: Refactor the code to use a protocol so you can test the dashboard without making network calls.
- Create a protocol named
WeatherProviderthat defines a method:fetchTemperature(for city: String, completion: @escaping (Double?) -> Void). - Update the
RealWeatherAPIclass to conform to this protocol. - Modify the
WeatherDashboardclass to accept aWeatherProviderin its initializer (Dependency Injection). - Create a
MockWeatherProviderthat allows you to manually set astubbedTemperaturevalue. - Write a small test snippet (or print statements) that proves the
WeatherDashboardcorrectly handles a scenario where theMockWeatherProviderreturnsnil(simulating a city not found).
There are no comments for now.