Swift 最简单的依赖注入框架。
- 线程安全。
- 只有大约 60 行源代码。
- 适用于任何平台,包括 Linux,不依赖于 Foundation。
- 不使用全局状态。
- 根本不需要任何配置。
- macOS / iOS / watchOS / tvOS / Linux
- Swift 4.2+
protocol Logger {
// ...
}
protocol Database {
// ...
}
class StdOutLogger: Logger {
init() { /*...*/ }
}
class CoreDataDatabase: Database {
static func setup() -> CoreDataDatabase { /*...*/ }
}
extension Service where T == Logger {
static let logger = Service(StdOutLogger.init)
static let database = Service(CoreDataDatabase.setup)
}
在应用代码中获取依赖项
在某处代码中创建一个Fetcher
实例
let fetcher = Fetcher.createDefault()
然后可以通过,例如,通过初始化器注入来传递这个实例。没错,你不需要在除将它们定义为Service
类的静态属性之外的地方注册你的服务。
当你需要依赖项时,只需调用
let logger = self.fetcher.fetch(.logger) // logger has the type Logger
let database = self.fetcher.fetch(.database) // database has the type Database
在测试中模拟服务
你的应用代码不需要(甚至不应该是)知道它是否在测试期间执行。当你需要模拟依赖项时,只需创建一个Fetcher
实例,并用它们的模拟重写需要的服务。
let fetcher = Fetcher
.create()
.addOverride(for: Service.logger, instance: MockLogger())
.addOverride(for: Service.database, instance: MockDatabase())
.done()
然后将这个实例传递到你要测试的单元中。
提取硬编码的依赖项
通常,你有一个很大的代码库,其中使用硬编码的依赖项(单例等)到处都是,你无法一次重构所有
代码。《DependencyFetcher》可以逐步引入到你的代码库中。
假设你有一个视图控制器这样做(相当常见的MVC代码)
class ViewController: UIViewControlelr {
var products: [Product] = []
override func viewDidLoad() {
super.viewDidLoad()
APIEndpoint.default.getProducts(onSuccess: { self.products = $0 },
onError: {error in /*...*/ })
}
}
这非常难测试。我们可以进行一点重构
protocol APIEndpointProtocol {
func getProducts(onSuccess: @escaping ([Product]) -> Void,
onError: @escaping (Error) -> Void)
}
extension APIEndpoint: APIEndpointProtocol {}
extension Service where T == APIEndpointProtocol {
static let apiEndpoint = Service(APIEndpoint.default)
}
class ViewController: UIViewControlelr {
var products: [Product] = []
var fetcher = Fetcher.createDefault()
private lazy var apiEndpoint = self.fetcher.fetch(.apiEndpoint)
override func viewDidLoad() {
super.viewDidLoad()
self.apiEndpoint.getProducts(onSuccess: { self.products = $0 },
onError: { error in /*...*/ })
}
}
现在我们可以很容易地测试我们的ViewController
class MockAPIEndpoint: APIEndpointProtocol {
let testProducts: [Product] = [
// ...
]
func getProducts(onSuccess: @escaping ([Product]) -> Void,
onError: @escaping (Error) -> Void) {
onSuccess(self.testProducts)
}
}
func testPopulatingViewControllerWithProducts() {
let vc = ViewController()
let mock = MockAPIEndpoint()
vc.fetcher = Fetcher
.create()
.addOverride(for: Service.apiEndpoint, instance: mock)
.done()
vc.viewDidLoad()
XCTAssertEqual(vc.products, mock.testProducts)
}
安装
Swift包管理器
在你Package.swift
文件中添加以下内容
dependencies: [
.package(url: "https://github.com/broadwaylamb/DependencyFetcher.git",
from: "1.0.0"),
]
CocoaPods
在您的 Podfile
中添加以下内容:
target 'MyApp' do
pod 'DependencyFetcher', '~> 1.0'
end