Open alexito4 opened 3 years ago
Setters can be declared as nonmutating
, which will remove the need to declare DefaultsAdapter
as a var
in order to mutate the values.
Hey @alexito4 - I just merged two PRs, one that adds nonmutating
keywords to the adapter's setters, and the other one for providing a protocol that you can implement to pass around testing adapters in your test suite. I'll be releasing 5.2.0 that should have both of these shortly. Please let me know if these help :)
Oh that sounds cool! I would made a task to check out the update. Thanks 😉
Thank you for this @sunshinejr!
In case anyone is interested, it took me a while but I figured out how to use SwiftyUserDefaults in the app and mock it in my tests. I'm using TCA which gives me an environment
object where I pass in a UserDefaults
object.
Then, I have this convenience extension method that I call on my passed UserDefaults
object in the app:
import Foundation
import SwiftyUserDefaults
extension UserDefaults {
/// Returns a SwiftyUserDefaults enhanced object that can be used like the `Defaults` object in the SwiftyUserDefaults documentation.
var swifty: DefaultsAdapter<DefaultsKeys> {
DefaultsAdapter<DefaultsKeys>(defaults: self, keyStore: .init())
}
}
Then, instead of Defaults.isFirstAppStart
I can now use environment.userDefaults.swifty.isFirstAppStart
everywhere in the app. I've setup a custom lint rule via AnyLint to ensure no developer uses Defaults.
directly.
In my test target, I have another extension for convenience:
import Foundation
extension UserDefaults {
static var test = UserDefaults(suiteName: "com.my.app.tests")!
}
This allows me to pass in UserDefaults.test
to my environment object in the test suite (in the app I pass UserDefaults.standard
instead). Additionally I call UserDefaults.test.removeAll()
in the setUp()
method of all my test classes. When I need to set a specific environment, I just set the values I need via UserDefaults.test.swifty.isFirstAppStart = false
(etc.).
class LoginTests: XCTestCase {
override func setUp() {
UserDefaults.test.removeAll()
}
func testForgotPassword() {
let store = TestStore(initialState: .init(), reducer: loginReducer, environment: .init(userDefaults: .test))
UserDefaults.test.swifty.isFirstAppStart = true
// my tests expecting `isFirstAppStart` to be `true`
}
// ...
}
I hope this helps someone out there!
@Jeehut: I would recommend you use removePersistentDomain as well. As is, I believe some tests might impact others eventually.
https://www.swiftbysundell.com/tips/avoiding-mocking-userdefaults/
The current design of DefaultsAdapter relies on it being used via the global variable Defaults which is a var. But using that global variable is not ideal for testing so I'm trying to use this library with typical DI.
Once doing that and storing a DefaultsAdapter as a property to be used things don't work as smoothly as expected. The problem is that the adapter is a struct and thus you can't mutate it easily. And in reality is not mutating any values so it's kind of misleading.
Changing it to a class shouldn't have any impact and would allow dynamic member lookup to be used in more places.