Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Swift Protocol Di Testing

ASecurity

Protocol-based dependency injection for testable Swift code — mock file system, network, and external APIs using focused protocols and Swift Testing.

12 stars
0 votes
0 copies
0 views
Added 9/28/2026
ai-agentsgoswifttestingcode-reviewgitapidatabasedocumentation

Works with

api

Security Analysis

A100/100

Scanned 9/28/2026

Install to Claude Code

$npx -y skills add Nmor/the-claude-council --skill swift-protocol-di-testing --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Swift Protocol Di Testing?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Swift Protocol Di Testing
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/nmor-swift-protocol-di-testing/badge)](https://www.skillsdirectory.com/skills/nmor-swift-protocol-di-testing)

More formats (shields.io, HTML) on the badges page.

Files
SKILL.md
---
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.
---

# Swift Protocol-Based Dependency Injection for Testing

> **Size budget: 15 KB** — `token-budget.mjs --check`.

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.

```swift
// 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

```swift
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

```swift
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.

```swift
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

```swift
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

## Purpose

Principal-level Swift testability: protocol-based dependency
injection, test doubles via in-memory protocol implementations
(stub / spy / fake), `swift-testing` framework (Swift 6+) +
XCTest legacy, async testing patterns with structured
concurrency, time injection via `Clock` protocol, `Sendable` +
actor-isolation for concurrent test setup, `@MainActor` test
boundaries, and the protocol-witness pattern (a struct of
closures) for cases where a protocol-with-extension is heavy.

**Negative scope** (NOT what this skill covers):

- Actor + persistence patterns — see `swift-actor-persistence`
- Generic Swift coding style — see `~/.claude/rules-library/swift/`
- UI testing (XCUITest) — out
- Snapshot testing — out (separate library: pointfree's
  `swift-snapshot-testing`)

## When NOT to use

- Pure-Foundation utility code with no external dependencies
- Throwaway experiments / Swift Playgrounds
- iOS app where the test target is genuinely absent (legacy
  Objective-C codebases sometimes)

## Standards Cited

- **Swift Language Reference 5.10 / 6.0** (docs.swift.org)
- **Swift Evolution SE-0306 (Actors)**
- **Swift Evolution SE-0302 (Sendable)**
- **Swift Evolution SE-0337 (Sendable strict)**
- **Swift Evolution SE-0401 (Remove preconcurrency)**
- **swift-testing framework** (github.com/swiftlang/swift-testing) —
  modern testing framework (Swift 6+)
- **XCTest** — legacy testing framework
- **Point-Free `swift-dependencies`** — community DI library
- **WWDC 2024: Meet swift-testing** — canonical introduction
- **OWASP ASVS 4.0.3 §1.4 (Architectural Documentation)** — DI
  pattern as architectural decision

## Anti-Patterns

| Pattern | Why bad | Correct alternative |
| --- | --- | --- |
| Direct `URLSession.shared` in business logic | Untestable; can't simulate network failures | Inject `URLSessionProtocol` |
| Singleton `Database.shared` | Single test pollutes the next | Inject `DatabaseProtocol` per test |
| `Date()` / `Date.now` in domain code | Time-dependent tests are flaky | Inject `Clock` (Swift 6) or `() -> Date` closure |
| `Bundle.main` inside reusable module | Coupled to app context | Inject `BundleProtocol` or pass URLs explicitly |
| `Task { ... }` without test scheduler | Async tests can't deterministically order completion | Use `swift-testing` confirmations + virtual time |
| Force-unwrapping in tests | Crashes obscure the real assertion | `#expect(value != nil)` then unwrap |
| Test setup repeated in every test case | DRY violation; refactor risk | Test fixtures / helpers per test target |
| Concrete class in production = concrete class in test | Test changes touch every test file | Protocol abstraction with `Test` and `Live` implementations |
| Hidden static state in protocol extension | Defaults that test can't override | Pure-instance protocol; defaults in initialiser |
| `XCTAssertEqual(result, expectedValue)` without async waiting | Asserts on stale state | `await fulfillment(of: ...)` OR swift-testing `#expect` with async |

## Verification Checklist

- [ ] Every external boundary (network, disk, time, RNG) has a
      protocol abstraction
- [ ] `Sendable` conformance on every type crossing actor
      boundaries
- [ ] Strict concurrency checking enabled (Swift 6 default)
- [ ] `Clock` protocol injected for any time-dependent logic
- [ ] Test target uses `swift-testing` (Swift 6+) OR XCTest
      consistently
- [ ] No force-unwrap (`!`) in test code outside explicit
      setup-time assertion
- [ ] Test isolation verified (random-order runs pass)
- [ ] Coverage ≥ 90% on touched files (per `extreme-lint-policy.md`)
- [ ] SwiftLint clean (force_unwrapping / force_try / empty_catch:
      error)
- [ ] No production singletons; all dependencies injected via
      initialiser
- [ ] Protocol-witness pattern considered for closure-heavy
      protocols
- [ ] `@MainActor` annotation on UI-touching code only

## Cross-References

- `~/.claude/skills/swift-actor-persistence/SKILL.md` — sister
  Swift concurrency skill
- `~/.claude/skills/coding-quality-rules/SKILL.md` — universal baseline
- `~/.claude/rules-library/swift/no-discards.md` — discards / force-unwrap
  bans
- `~/.claude/rules-library/common/no-ambient-globals.md` — DI principle
- `~/.claude/rules-library/common/local-testability.md` — testable-before-
  write mandate
- `~/.claude/agents/code-reviewer.md` — Council Division 3
- `~/.claude/agents/tdd-guide.md` — Council Division 5
- Swift Forums — Testing topic (forums.swift.org)

## Why this skill exists

Swift codebases that depend directly on `URLSession.shared`,
`Date()`, `Bundle.main`, and singleton databases become testable
only via heavy mocking libraries (or not at all). The protocol-
based DI pattern lets the codebase ship the same code that runs
in production, in tests, and in SwiftUI previews — with each
environment supplying its own implementation. The patterns above
codify the principal-level posture: protocol abstraction at every
boundary, time injection, structured concurrency in tests,
swift-testing for the modern API. Teams that adopt these have
fast, deterministic, parallel test suites; teams that don't ship
flaky tests that get disabled and then forgotten.

## Learning hooks

Per `~/.claude/rules/common/continuous-learning-mandate.md`:

**Signals to watch**:

- Concrete dependency hardcoded in initializer (testability weakening — protocol-DI absent)
- Mock missing protocol conformance (test doesn't compile when protocol evolves)
- Test using singleton-shared `URLSession.shared` instead of injected `URLProtocol` stub
- Preview crashes because protocol has no preview-friendly mock
- `@Mockable` macros / Mockingbird absent — manual mock duplication
- Test for error path missing because mock can only return success
- DI container (Resolver / Factory / Needle) used inconsistently across modules

**Refinement candidates**:

- New protocol-DI pattern row when SwiftUI / async/await ships a new feature
- New cross-reference when a sister skill (swift-actor-persistence, tdd-workflow) adds a Swift gate
- New mock-template row when a recurring stub shape emerges
- Tightening of the protocol-first rule when concrete-deps regression recurs

Attribution

NmorNmor
View sourceMore from Nmor →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Ultra-compressed communication mode that cuts output tokens while keeping technical accuracy. Levels: lite, full, ultra and the wenyan variants. Use for /caveman, "caveman mode", "talk like caveman", "be brief" or "less tokens".

1074701 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

695601 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3351 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

691 votes

math-skill

A comprehensive mathematical reasoning skill for AI assistants — handles arithmetic to research-level problems with rigorous step-by-step reasoning, systematic verification, and transparent uncertainty handling

381 votes
View all in ai-agents →