One way of doing it is to wrap your UserDefaults
in a protocol and expose what you need.
Then you create a an actual class which conforms to that protocol and which uses UserDefaults
You can then instantiate your UserDefaultsService
with that class.
When you need to test, you can create a mock conforming to the same protocol and use that instead. That way you won't "pollute" your UserDefaults
.
The above might seem like a bit of a mouthful so lets break it down.
Note that in the above I removed the "static" part as well, it didn't seem necessary, and it made it easier without it, hope that is OK
1. Create a Protocol
This should be all you are interested in exposing
protocol SettingsContainer {
var token: String? { get set }
}
2. Create an Actual Class
This class will be used with UserDefaults
but it is "hidden" behind the protocol.
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. Instantiate UserDefaultsService With That Class
Now we create an instance of your UserDefaultsService
which has an object conforming to the SettingsContainer
protocol.
The beauty is that you can change the provided class later on...for instance when testing.
The UserDefaultsService
does not know - or care - whatever the SettingsContainer
actually does with the value, as long as it can give and take a token
, then the UserDefaultsService
is happy.
Here's how that looks, note that we are passing a default parameter, so we don't even have to pass a SettingsContainer
unless we have to.
class UserDefaultsService {
private var settingsContainer: SettingsContainer
init(settingsContainer: SettingsContainer = UserDefaultsContainer()) {
self.settingsContainer = settingsContainer
}
var token: String? {
get {
return settingsContainer.token
}
set {
settingsContainer.token = newValue
}
}
}
You can now use a new UserDefaultsService
like so:
let userDefaultsService = UserDefaultsService()
print("token: \(userDefaultsService.token)")
4 Testing
"Finally" you say :)
To test the above, you can create a MockSettingsContainer
conforming to the SettingsContainer
class MockSettingsContainer: SettingsContainer {
var token: String?
}
and pass that to a new UserDefaultsService
instance in your test target.
let mockSettingsContainer = MockSettingsContainer()
let userDefaultsService = UserDefaultsService(settingsContainer: mockSettingsContainer)
And can now test that your UserDefaultsService
can actually save and retrieve data without polluting UserDefaults
.
Final Notes
The above might seem like a lot of work, but the important thing to understand is:
- wrap 3rd party components (like
UserDefaults
) behind a protocol so you are free to change them later on if so needed (for instance when testing).
- Have dependencies in your classes that uses these protocols instead of "real" classes, that way you - again - are free to change the classes. As long as they conform to the protocol, all is well :)
Hope that helps.