swift-protocol-di-testing
affaan-m/everything-claude-code
Protocol-based dependency injection for testable Swift code with mocks for file system, network, and external APIs.
What is swift-protocol-di-testing?
Patterns for abstracting external dependencies (file system, network, iCloud) behind small, focused protocols to enable deterministic testing without I/O. Use this when writing Swift code that accesses external systems and needs testable architecture with Swift concurrency.
- Define small, focused protocols for each external concern (file system, network, bookmarks)
- Create default production implementations and mock implementations for testing
- Inject dependencies via default parameters so production uses real implementations by default
- Write deterministic tests using Swift Testing that simulate errors and edge cases
- Design testable architecture compatible with actors and Sendable conformance
How to install swift-protocol-di-testing
npx skills add https://github.com/affaan-m/everything-claude-code --skill swift-protocol-di-testingHow to use swift-protocol-di-testing
- 1.Define a small, focused protocol for each external dependency (e.g., FileSystemProviding, FileAccessorProviding)
- 2.Create a default production implementation conforming to the protocol
- 3.Create a mock implementation with configurable properties for error simulation
- 4.Inject dependencies into your types using default parameters pointing to production implementations
- 5.In tests, pass mock instances to test error handling and edge cases
- 6.Use Swift Testing assertions to verify behavior with mocked dependencies
Use cases
- Testing file system access without triggering real I/O or permission errors
- Mocking network failures and error conditions in unit tests
- Building modules that work across app, test, and SwiftUI preview contexts
- Testing error handling paths that are difficult to trigger in production
- Verifying iCloud sync logic with deterministic mock data
- Swift developers building testable architectures
- Teams using Swift concurrency (actors, structured concurrency)
- Developers working with file system, network, or external API integrations
- QA engineers needing to test error paths systematically
swift-protocol-di-testing FAQ
No. Only mock external dependencies (file system, network, APIs). Mocking internal types adds unnecessary complexity without testing benefits.
Small protocols follow single responsibility, are easier to mock, and allow types to depend only on what they actually need. Large 'god protocols' become hard to implement and test.
Yes, if the protocols are used across actor boundaries. Sendable conformance is required for Swift concurrency safety and is a best practice for dependency injection protocols.
Design mock implementations with configurable error properties (e.g., readError, writeError) that you set in tests to simulate specific failure conditions.
Yes. Production code uses real implementations by default; only tests need to specify mocks. This keeps production code clean while enabling flexible testing.
Full instructions (SKILL.md)
Source of truth, from affaan-m/everything-claude-code.
name: swift-protocol-di-testing description: Protocol-based dependency injection for testable Swift code — mock file system, network, and external APIs using focused protocols and Swift Testing. metadata: origin: ECC
Swift Protocol-Based Dependency Injection for Testing
Patterns for making Swift code testable by abstracting external dependencies (file system, network, iCloud) behind small, focused protocols. Enables deterministic tests without I/O.
When to Activate
- Writing Swift code that accesses file system, network, or external APIs
- Need to test error handling paths without triggering real failures
- Building modules that work across environments (app, test, SwiftUI preview)
- Designing testable architecture with Swift concurrency (actors, Sendable)
Core Pattern
1. Define Small, Focused Protocols
Each protocol handles exactly one external concern.
// File system access
public protocol FileSystemProviding: Sendable {
func containerURL(for purpose: Purpose) -> URL?
}
// File read/write operations
public protocol FileAccessorProviding: Sendable {
func read(from url: URL) throws -> Data
func write(_ data: Data, to url: URL) throws
func fileExists(at url: URL) -> Bool
}
// Bookmark storage (e.g., for sandboxed apps)
public protocol BookmarkStorageProviding: Sendable {
func saveBookmark(_ data: Data, for key: String) throws
func loadBookmark(for key: String) throws -> Data?
}
2. Create Default (Production) Implementations
public struct DefaultFileSystemProvider: FileSystemProviding {
public init() {}
public func containerURL(for purpose: Purpose) -> URL? {
FileManager.default.url(forUbiquityContainerIdentifier: nil)
}
}
public struct DefaultFileAccessor: FileAccessorProviding {
public init() {}
public func read(from url: URL) throws -> Data {
try Data(contentsOf: url)
}
public func write(_ data: Data, to url: URL) throws {
try data.write(to: url, options: .atomic)
}
public func fileExists(at url: URL) -> Bool {
FileManager.default.fileExists(atPath: url.path)
}
}
3. Create Mock Implementations for Testing
public final class MockFileAccessor: FileAccessorProviding, @unchecked Sendable {
public var files: [URL: Data] = [:]
public var readError: Error?
public var writeError: Error?
public init() {}
public func read(from url: URL) throws -> Data {
if let error = readError { throw error }
guard let data = files[url] else {
throw CocoaError(.fileReadNoSuchFile)
}
return data
}
public func write(_ data: Data, to url: URL) throws {
if let error = writeError { throw error }
files[url] = data
}
public func fileExists(at url: URL) -> Bool {
files[url] != nil
}
}
4. Inject Dependencies with Default Parameters
Production code uses defaults; tests inject mocks.
public actor SyncManager {
private let fileSystem: FileSystemProviding
private let fileAccessor: FileAccessorProviding
public init(
fileSystem: FileSystemProviding = DefaultFileSystemProvider(),
fileAccessor: FileAccessorProviding = DefaultFileAccessor()
) {
self.fileSystem = fileSystem
self.fileAccessor = fileAccessor
}
public func sync() async throws {
guard let containerURL = fileSystem.containerURL(for: .sync) else {
throw SyncError.containerNotAvailable
}
let data = try fileAccessor.read(
from: containerURL.appendingPathComponent("data.json")
)
// Process data...
}
}
5. Write Tests with Swift Testing
import Testing
@Test("Sync manager handles missing container")
func testMissingContainer() async {
let mockFileSystem = MockFileSystemProvider(containerURL: nil)
let manager = SyncManager(fileSystem: mockFileSystem)
await #expect(throws: SyncError.containerNotAvailable) {
try await manager.sync()
}
}
@Test("Sync manager reads data correctly")
func testReadData() async throws {
let mockFileAccessor = MockFileAccessor()
mockFileAccessor.files[testURL] = testData
let manager = SyncManager(fileAccessor: mockFileAccessor)
let result = try await manager.loadData()
#expect(result == expectedData)
}
@Test("Sync manager handles read errors gracefully")
func testReadError() async {
let mockFileAccessor = MockFileAccessor()
mockFileAccessor.readError = CocoaError(.fileReadCorruptFile)
let manager = SyncManager(fileAccessor: mockFileAccessor)
await #expect(throws: SyncError.self) {
try await manager.sync()
}
}
Best Practices
- Single Responsibility: Each protocol should handle one concern — don't create "god protocols" with many methods
- Sendable conformance: Required when protocols are used across actor boundaries
- Default parameters: Let production code use real implementations by default; only tests need to specify mocks
- Error simulation: Design mocks with configurable error properties for testing failure paths
- Only mock boundaries: Mock external dependencies (file system, network, APIs), not internal types
Anti-Patterns to Avoid
- Creating a single large protocol that covers all external access
- Mocking internal types that have no external dependencies
- Using
#if DEBUGconditionals instead of proper dependency injection - Forgetting
Sendableconformance when used with actors - Over-engineering: if a type has no external dependencies, it doesn't need a protocol
When to Use
- Any Swift code that touches file system, network, or external APIs
- Testing error handling paths that are hard to trigger in real environments
- Building modules that need to work in app, test, and SwiftUI preview contexts
- Apps using Swift concurrency (actors, structured concurrency) that need testable architecture
Related skills
More from affaan-m/everything-claude-code and the wider catalog.
security-review
Security checklist and patterns for authentication, input validation, secrets, and sensitive features.
golang-patterns
Idiomatic Go patterns, best practices, and conventions for building robust, efficient, and maintainable applications.
coding-standards
Baseline coding conventions for naming, readability, immutability, and quality across projects.
frontend-patterns
React and Next.js patterns for components, state management, performance, and modern frontend practices.
backend-patterns
REST/GraphQL API design, database optimization, and server-side patterns for Node.js, Express, and Next.js.
golang-testing
Go testing patterns: table-driven tests, subtests, benchmarks, fuzzing, and TDD methodology.