Let's say I have a very common use case for a property wrapper using UserDefaults
.
@propertyWrapper
struct DefaultsStorage<Value> {
private let key: String
private let storage: UserDefaults
var wrappedValue: Value? {
get {
guard let value = storage.value(forKey: key) as? Value else {
return nil
}
return value
}
nonmutating set {
storage.setValue(newValue, forKey: key)
}
}
init(key: String, storage: UserDefaults = .standard) {
self.key = key
self.storage = storage
}
}
I am now declaring an object that would hold all my values stored in UserDefaults
.
struct UserDefaultsStorage {
@DefaultsStorage(key: "userName")
var userName: String?
}
Now when I want to use it somewhere, let's say in a view model, I would have something like this.
final class ViewModel {
func getUserName() -> String? {
UserDefaultsStorage().userName
}
}
Few questions arise here.
- It seems that I am obliged to use
.standard
user defaults in this case. How to test that view model using other/mocked instance ofUserDefaults
? - How to test that property wrapper using other/mocked instance of
UserDefaults
? Do I have to create a new type that is a clean copy of the above'sDefaultsStorage
, pass mockedUserDefaults
and test that object?
struct TestUserDefaultsStorage {
@DefaultsStorage(key: "userName", storage: UserDefaults(suiteName: #file)!)
var userName: String?
}
As @mat already mentioned in the comments, you need a
protocol
to mockUserDefaults
dependency. Something like this will do:Then you can change your
DefaultsStorage
propertyWrapper to use aUserDefaultsStorage
reference instead ofUserDefaults
:After that a mock
UserDefaultsStorage
might look like this:And to test
DefaultsStorage
, pass an instance ofUserDefaultsStorageMock
as its storage parameter: