I can successfully override the UserDefaults value by setting it with XCUIApplication.launchArguments. But based on this link and the behavior I've noticed, once I override the UserDefaults value all changes the app tries to make to it are ignored.
How do I set an initial testing value for a UserDefaults key but still allow my app to then change the value? How can I correctly test my app's response to UserDefaults updates and avoid flaky tests that have to run in order?
More background/detail:
In my iOS app, I am using UserDefaults to dictate how I render some things that should persist across app sessions. Example: I have a setting that tracks if users want to see time of day vs a countdown timer to an alarm (e.g. display "3:00pm" vs "in 1 hour"). Let's call this a boolean in UserDefaults with the key showClockTime Users can change their preferences from a 'Settings' page that sets the UserDefaults value, and my other UIViewControllers check the UserDefaults value in viewWillAppear() so that I display the right thing.
Rather than add the same checks across the app for if this boolean is nil, true, or false I am checking it in SceneDelegate and if the value is nil set it to whatever default I prefer and then assume it's never nil in the rest of my app. (I know booleans default to returning false instead of nil if you use UserDefaults.standard.bool("showClockTime") but I have some String settings I'd like to test too so just go with it.)
I am trying to write integration tests to test:
- If the user has never opened the app, do I set the correct default value in SceneDelegate before they see my first UIViewController? (meaning that: if the value is initially
nil, I want to verify I set it totrueand the UI renders for thetruesetting instead of the defaultfalseit would be otherwise) - UserDefaults persist across test runs, so to get a non-flaky-test, how do I set the test's initial UserDefaults value but also test that my Settings page correctly updates the value. For example, I have a switch that I want to toggle "off" (so
showClockTime = false) and I need to know that it starts as "on" before my test runs (soshowClockTimemust be initiallytrueor toggling won't do what I expect).
Not sure it matters, but other info about my app:
- This is a vanilla Swift app, I'm not using any libraries
Tried to set UserDefaults key showClockTime to nil as it will be on first-ever app launch and I can't ever change the setting later as my test runs.
I found a workaround but I'm not sure this is the best approach:
Overriding a UserDefaults key only makes that key immutable for the rest of the test, so instead override a test key and inside AppDelegate translate that override to the actual key that should be overridden.
In UI test:
Then in AppDelegate:
And you could do this for each flag you want to override but still change in your test.
Then in your test:
It works for my case, but I don't like that testing code is inside AppDelegate. I tried to mitigate risk of this accidentally getting called in prod and changing real user settings by adding the second flag that specifies it's a test.