swift-dependencies
Type-safe dependency injection for Swift: the @Dependency property wrapper resolves values through a live, preview, and test chain, with scoped overrides supplied by withDependencies.
Key Features
@Dependencyproperty wrapper — Reaches a dependency by KeyPath or key type, with no constructor threading through the call graph.- Live, preview, and test resolution — Each
Dependency.Keyresolves throughtestValue → previewValue → liveValue, selected by the currentDependency.Contextmode. - Scoped overrides —
withDependenciesreplaces values for the duration of a synchronous or asynchronous operation; nested scopes inherit and override outer values. - Typed throws preserved — Operations carry their concrete error type through the scope (
throws(E)), so the API surface introduces noany Error. - Strict keys —
Dependency.Key.Strictmakes a dependency fail fast in tests unless an explicit override is supplied. - Escaping-closure capture —
Dependency.Continuationcarries the active context into timers, callbacks, and other escaping closures. - Task-local storage — Resolution is backed by task-local state rather than global mutable state.
Quick Start
Declare a dependency with a live value and a deterministic test value, register it for KeyPath access, and reach it from feature code through @Dependency. The feature type takes no initializer parameter for the collaborator:
import Dependencies
struct RandomNumbers: Sendable {
var next: @Sendable () -> Int
}
extension RandomNumbers: Dependency.Key {
static var liveValue: RandomNumbers { RandomNumbers { Int.random(in: 1...100) } }
static var testValue: RandomNumbers { RandomNumbers { 42 } } // deterministic
}
extension Dependency.Values {
var randomNumbers: RandomNumbers {
get { self[RandomNumbers.self] }
set { self[RandomNumbers.self] = newValue }
}
}
struct DiceGame {
@Dependency(\.randomNumbers) var randomNumbers
func roll() -> Int { randomNumbers.next() }
}
In a test, pin the dependency to a fixed value for one scope — DiceGame is never modified or re-initialized to accept the substitute:
let outcome = withDependencies {
$0.randomNumbers = RandomNumbers { 6 }
} operation: {
DiceGame().roll()
}
// outcome == 6
Installation
dependencies: [
.package(url: "https://github.com/swift-foundations/swift-dependencies.git", branch: "main")
]
.target(
name: "YourTarget",
dependencies: [
.product(name: "Dependencies", package: "swift-dependencies")
]
)
Requires Swift 6.3.1 and macOS 26 / iOS 26 / tvOS 26 / watchOS 26 / visionOS 26.
Architecture
Two library products. Module import names replace spaces with underscores (import Dependencies, import Dependencies_Test_Support).
| Product | When to import |
|---|---|
Dependencies | App and library code: the @Dependency wrapper, withDependencies, prepareDependencies, and the Dependency.Key / Dependency.Values / Dependency.Context surface. |
Dependencies Test Support | Test targets: re-exports Dependencies and adds Swift Testing traits (.dependencies, .dependency(_:_:)) for per-test and per-suite dependency isolation. |
A ready-made \.clock dependency (a real clock when live, an immediate clock
in tests and previews) lives in
swift-clocks-dependencies.
Community
Discussion thread will be created at first public release.
License
Apache 2.0. See LICENSE.