Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider making DefaultsAdapter a class #268

Open
alexito4 opened this issue Dec 15, 2020 · 5 comments
Open

Consider making DefaultsAdapter a class #268

alexito4 opened this issue Dec 15, 2020 · 5 comments

Comments

@alexito4
Copy link

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.

@agmike
Copy link

agmike commented Dec 28, 2020

Setters can be declared as nonmutating, which will remove the need to declare DefaultsAdapter as a var in order to mutate the values.

@sunshinejr
Copy link
Owner

sunshinejr commented Feb 23, 2021

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 :)

@alexito4
Copy link
Author

alexito4 commented Mar 3, 2021

Oh that sounds cool! I would made a task to check out the update. Thanks 😉

@Jeehut
Copy link

Jeehut commented Jul 26, 2021

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!

@VillSkog
Copy link

@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/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants