A modular, testable collection of lightweight wrappers for common Foundation types, designed for seamless use with swift-dependencies. This package makes it easy to mock, inject, and override behaviours like UserDefaults, Bundle, and file system operations in both production and test environments.
Add this package via Swift Package Manager:
.product(name: "FoundationDependencies", package: "foundation-dependencies")Comprehensive documentation is available via Swift Package Index:
➡️ Browse Documentation on Swift Package Index
| Client | Description |
|---|---|
mainBundleClient |
A wrapper around Bundle, exposing APIs for loading resources via a BundleResourceProvider abstraction. |
userDefaultsClient |
A testable interface for UserDefaults, built using UserDefaultsStoreProtocol. Ideal for dependency injection and isolating persistent state in tests. Your app must register a live store at launch (see below). |
fileSystemClient |
A robust file system interface supporting operations such as reading, writing, copying, moving, and deleting files or directories. Suitable for sandboxed storage and fully mockable for tests. |
fileSystemResourceClient |
A factory for creating typed file stores that conform to FileSystemOperations. Supports saving and loading Codable values and binary data into specific folders and subfolders, without exposing raw file system APIs. |
loggerClient |
An interface to os.Logger, auto-populated with the MainBundle bundle identifier (even if you're logging outside the main bundle). |
Note
Many additional dependencies likedate,uuid, andcalendarare provided transitively viaswift-dependencies.
See the complete list of built-ins.
userDefaultsClient is the one client that does nothing useful until your app registers a live store. UserDefaultsKey conforms to TestDependencyKey only, and that is deliberate: the suite name is app-specific, so the package cannot supply a live value and still build in isolation.
Register the store once, as early in the app lifecycle as you can:
import Dependencies
import FoundationDependencies
import SwiftUI
@main
struct MyApp: App {
init() {
guard let store = UserDefaultsLiveStore(suiteName: "group.com.example.myapp") else {
preconditionFailure("group.com.example.myapp is not a usable suite name")
}
prepareDependencies {
$0.userDefaultsClient = store
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}This is the recommended route. It requires no conformance of your own, and it is the only step needed before @Dependency(\.userDefaultsClient) resolves to real storage anywhere in your app.
When a live context asks for a key that has no DependencyKey conformance, swift-dependencies falls back to that key's testValue. Here that fallback is UserDefaultsTestStore, which is an in-memory dictionary. Reads and writes still appear to succeed, so nothing looks broken, but nothing is persisted and everything is gone at the next launch.
A debug build reports the missing registration as a runtime warning. In a release build that report is compiled out, so the fallback is completely silent and the only symptom is that your users lose their data.
An app group identifier such as group.com.example.myapp is the intended form, and it is what lets an app extension read the same values.
Do not pass your app's own bundle identifier or NSGlobalDomain. Foundation refuses both, so UserDefaultsLiveStore(suiteName:) returns nil and no store is produced at all. That is why the example above handles the optional rather than assigning it straight through, and why it fails loudly when it is nil. A suite name is a compile-time constant, so a nil result is a mistake in the name itself and will be nil on every launch on every device. Substituting a fallback store would hide it and move the symptom to wherever the values are later read.
No supported suite name reaches the app's own defaults. When the values are not shared with another process, register UserDefaultsLiveStore.standard instead, which reads and writes those domains and is not failable.
Conforming the key in your own module also works:
import Dependencies
import FoundationDependencies
extension UserDefaultsKey: @retroactive DependencyKey {
public static let liveValue: any UserDefaultsStoreProtocol = UserDefaultsLiveStore.standard
}Prefer prepareDependencies. A stored property has nowhere sensible to handle a failable initialiser, so this route is awkward for anything but standard. A retroactive conformance is also declared in your module while belonging to this package's type, so if FoundationDependencies ever declares DependencyKey itself, every consumer holding a copy of it hits a duplicate conformance and stops compiling.
See CONTRIBUTING.md before running the test suite locally. It records a toolchain requirement that applies to running the tests and not to using this package, along with the unhelpful errors you get when it is unmet.