一种方法是将您UserDefaults
的协议包装在协议中并公开您需要的内容。
然后创建一个符合该协议并使用的实际类UserDefaults
然后,您可以UserDefaultsService
使用该类实例化您的。
当您需要测试时,您可以创建一个符合相同协议的模拟并使用它来代替。这样你就不会“污染”你的UserDefaults
.
上面的内容可能看起来有点拗口,所以让我们分解一下。
请注意,在上面我也删除了“静态”部分,这似乎没有必要,没有它会更容易,希望没关系
1. 创建协议
这应该是您有兴趣公开的所有内容
protocol SettingsContainer {
var token: String? { get set }
}
2.创建一个实际的类
此类将与协议一起使用,UserDefaults
但它“隐藏”在协议后面。
class UserDefaultsContainer {
private struct Keys {
static let token = "partageTokenKey"
}
}
extension UserDefaultsContainer: SettingsContainer {
var token: String? {
get {
return UserDefaults.standard.string(forKey: Keys.token)
}
set {
UserDefaults.standard.set(newValue, forKey: Keys.token)
}
}
}
3. 使用该类实例化 UserDefaultsService
现在我们创建一个你的实例,UserDefaultsService
它有一个符合SettingsContainer
协议的对象。
美妙之处在于您可以稍后更改提供的类......例如在测试时。
TheUserDefaultsService
不知道 - 也不关心 - 无论SettingsContainer
实际对 value 做了什么,只要它可以给予和接受 a token
,那么 theUserDefaultsService
就是快乐的。
看起来是这样的,注意我们正在传递一个默认参数,所以我们甚至不必传递 aSettingsContainer
除非我们必须。
class UserDefaultsService {
private var settingsContainer: SettingsContainer
init(settingsContainer: SettingsContainer = UserDefaultsContainer()) {
self.settingsContainer = settingsContainer
}
var token: String? {
get {
return settingsContainer.token
}
set {
settingsContainer.token = newValue
}
}
}
您现在可以UserDefaultsService
像这样使用新的:
let userDefaultsService = UserDefaultsService()
print("token: \(userDefaultsService.token)")
4 测试
“终于”你说:)
要测试上述内容,您可以创建一个MockSettingsContainer
符合SettingsContainer
class MockSettingsContainer: SettingsContainer {
var token: String?
}
并将其传递给UserDefaultsService
测试目标中的新实例。
let mockSettingsContainer = MockSettingsContainer()
let userDefaultsService = UserDefaultsService(settingsContainer: mockSettingsContainer)
现在可以测试您是否UserDefaultsService
可以实际保存和检索数据而不会造成污染UserDefaults
。
最后的笔记
上面的内容可能看起来很多工作,但要理解的重要一点是:
- 将 3rd 方组件(如
UserDefaults
)包装在协议后面,以便您以后可以根据需要(例如在测试时)随意更改它们。
- 在您的类中使用这些协议而不是“真实”类的依赖项,这样您 - 再次 - 可以自由更改类。只要它们符合协议,一切都很好:)
希望有帮助。