Catalog
affaan-m/swift-protocol-di-testing

affaan-m

swift-protocol-di-testing

Protocol-based dependency injection for testable Swift code — mock file system, network, and external APIs using focused protocols and Swift Testing. Use when Swift code needs testing and file system, network, or external APIs must be mocked.

NewUpdated Sep 9, 2026

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 DEBUG conditionals instead of proper dependency injection
  • Forgetting Sendable conformance 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
Files1
1 files · 1.0 KB

Select a file to preview

Overall Score

92/100

Grade

A

Excellent

Grades are signals, not a certification. Always review a skill yourself before use.

Safety

95

Quality

90

Clarity

92

Completeness

88

Summary

This skill teaches protocol-based dependency injection (DI) patterns for Swift, enabling testable code by abstracting file system, network, and external API dependencies behind small, focused protocols. It provides concrete patterns for creating production implementations and mock doubles, integrating with Swift Testing and Swift concurrency (actors, Sendable).

Detected Capabilities

protocol designtest code generationmock implementation patternsdependency injection setuperror simulation for testsswift testing framework integrationactor and sendable pattern guidance

Trigger Keywords

Phrases that agents use to match this skill to user intent.

mock file systemtest swift codeprotocol dependency injectionswift testingmock network requestsactor testabilityerror simulation

Use Cases

  • Test error handling paths without triggering real I/O failures
  • Mock file system access in unit tests
  • Mock network and external API calls deterministically
  • Build testable Swift modules that work across app, test, and SwiftUI preview contexts
  • Design testable architecture with Swift actors and structured concurrency
  • Write deterministic tests for iCloud sync, local storage, and network operations

Quality Notes

  • Excellent pedagogical structure with clear progression from protocol definition through mocking to test examples
  • All code examples are complete, compilable, and follow Swift best practices
  • Sendable conformance explicitly addressed — critical for actor safety
  • Strong anti-patterns section proactively guides against common mistakes
  • Clear single-responsibility principle for protocols prevents over-engineering
  • Error simulation patterns (configurable error properties) make failure testing practical
  • Default parameter injection pattern elegantly separates production and test paths without #if DEBUG
  • Uses modern Swift Testing framework (not XCTest) aligned with current Swift evolution
  • Comprehensive coverage of scoping: file system, network, external APIs, and iCloud
  • All examples include proper async/await and concurrency semantics
Model: claude-haiku-4-5-20251001Analyzed: Sep 9, 2026

Reviews

Add this skill to your library to leave a review.

No reviews yet

Be the first to share your experience.

Version History

  1. v2.0

    Contract changed: description

    ✦ AIClarifies skill activation criteria: added explicit guidance to use when Swift code needs testing and file system, network, or external APIs must be mocked.

    triggering2026-09-09

    LATEST
  2. v1.2

    Content updated

    ✦ AINo behavioral changes detected.

    2026-07-14

    View This Version
  3. v1.1

    Content updated

    ✦ AIAdds LICENSE file.

    2026-04-20

    View This Version
  4. v1.0

    Seeded from github.com/affaan-m/everything-claude-code

    2026-03-16

    View This VersionInitial version

Use affaan-m/swift-protocol-di-testing in your dev environment

Command Palette

Search for a command to run...