Using a Protocol to Facilitate Mocking Services for Testing
M.Shahzad Qamar
Seasoned Software Engineer | Mobile App Specialist | 12 Years Experience | Swift & iOS Expertise in Core Functionality
In the refactored code, we use protocol-oriented programming to define a contract for file fetching services. This makes it easy to replace the real implementation (FileService) with a mock implementation (MockFileService) when writing unit tests.
1. Why Use a Protocol?
A protocol allows us to:
2. How It Works in the Code
Step 1: Define a Protocol
protocol FileServiceProtocol {
func fetchFiles(withPrefix prefix: String) -> [String]
}
Step 2: Implement the Protocol in the Real Service
class FileService: FileServiceProtocol {
func fetchFiles(withPrefix prefix: String) -> [String] {
let fm = FileManager.default
guard let path = Bundle.main.resourcePath else { return [] }
do {
let files = try fm.contentsOfDirectory(atPath: path)
return files.filter { $0.hasPrefix(prefix) }
} catch {
print(AppError.fileNotFound.localizedDescription)
return []
}
}
}
Step 3: Inject the Dependency into the ViewModel
class ImageListViewModel {
private let fileService: FileServiceProtocol
private(set) var listOfFiles: [String] = []
init(fileService: FileServiceProtocol = FileService()) {
self.fileService = fileService
} func loadImages() {
listOfFiles = fileService.fetchFiles(withPrefix: "nssl")
}
}
3. Creating a Mock Service for Testing
For unit testing, we need to replace the real file service with a mock service that returns predefined data.
class MockFileService: FileServiceProtocol {
func fetchFiles(withPrefix prefix: String) -> [String] {
return ["nssl001.jpg", "nssl002.jpg", "nssl003.jpg"]
}
}
4. Writing a Unit Test
We can now write a unit test for ImageListViewModel using the mock service.
class ImageListViewModelTests: XCTestCase {
func testLoadImages() {
// Arrange: Use MockFileService instead of FileService
let mockService = MockFileService()
let viewModel = ImageListViewModel(fileService: mockService) // Act: Load images
viewModel.loadImages() // Assert: Check if mock data is correctly loaded
XCTAssertEqual(viewModel.listOfFiles.count, 3)
XCTAssertEqual(viewModel.listOfFiles[0], "nssl001.jpg")
}
}
5. Benefits of This Approach
Faster Tests — No real file system access, just predefined data. More Reliable — Tests won’t fail due to external file changes. Easier Debugging — You control the test data, making failures easier to analyze. Better Maintainability — You can change FileService implementation without affecting the ViewModel.