Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reset @AppStorage before test?

I am trying to clear User Defaults before a test but removePersistentDomain isn't working.

override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
        if let bundleID = Bundle.main.bundleIdentifier {
            print("BUNDLE", bundleID)
            print("UserDefaults", UserDefaults.standard.dictionaryRepresentation().keys.description)
            
            UserDefaults.standard.removePersistentDomain(forName: "com.example.App")
            UserDefaults.standard.removePersistentDomain(forName: bundleID) // "com.example.AppUITests"
            
            print("UserDefaults", UserDefaults.standard.dictionaryRepresentation().keys.description)
        }

        // In UI tests it is usually best to stop immediately when a failure occurs.
        continueAfterFailure = false

        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
    }

I tried printing the keys and none of the keys I defined show up.

UserDefaults ["AppleLanguages", "AddingEmojiKeybordHandled", "AKLastIDMSEnvironment", "AppleKeyboardsExpanded", "ApplePasscodeKeyboards", "XCTEmitOSLogs", "NSInterfaceStyle", "AppleLanguagesDidMigrate", "XCTTelemetryFlushTimeout", "NSLanguages", "XCTDisableTelemetryLogging", "PKLogNotificationServiceResponsesKey", "AppleKeyboards", "AppleLanguagesSchemaVersion", "AKLastLocale", "XCUIApplicationCrashReportTimeoutDefault", "XCTIDEConnectionTimeout"]
UserDefaults ["XCTEmitOSLogs", "AddingEmojiKeybordHandled", "AKLastLocale", "NSLanguages", "AppleLanguagesDidMigrate", "AppleKeyboards", "NSInterfaceStyle", "XCUIApplicationCrashReportTimeoutDefault", "XCTDisableTelemetryLogging", "AppleLanguages", "ApplePasscodeKeyboards", "AppleLanguagesSchemaVersion", "PKLogNotificationServiceResponsesKey", "XCTTelemetryFlushTimeout", "AppleKeyboardsExpanded", "XCTIDEConnectionTimeout", "AKLastIDMSEnvironment"]

It seems that the keys for app storage are stored elsewhere. What domain name/suite name does @AppStorage use?

Edit:

If I run the test, the saved value @AppStorage("someString") is displayed, but it isn't part of UserDefaults.standard.dictionaryRepresentation().

func testExample() throws {
    // UI tests must launch the application that they test.
    let app = XCUIApplication()
    app.launch()

    // Use XCTAssert and related functions to verify your tests produce the correct results.
    
    sleep(10)
}

Edit 2: I created a new project with tests included.

import SwiftUI

struct ContentView: View {
    
    @AppStorage("test") var test = "Test"
    var body: some View {
        VStack {
            Text(test)
            Button(action: {
                test = "New value"
            }) {
                Text("Change value")
            }
            Button(action: {
                if let bundleID = Bundle.main.bundleIdentifier{
                    UserDefaults.standard.removePersistentDomain(forName: bundleID)
                }
            }){
                Text("Reset storage")
            }
        }
    }
}
import XCTest

final class TestStorageUITests: XCTestCase {

    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
        for e in UserDefaults.standard.dictionaryRepresentation(){
            print("UserDefault", e.key, e.value)
        }
        
        // In UI tests it is usually best to stop immediately when a failure occurs.
        continueAfterFailure = false

        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    func testExample() throws {
        // UI tests must launch the application that they test.
        let app = XCUIApplication()
        app.launch()

        // Use XCTAssert and related functions to verify your tests produce the correct results.
        sleep(10)
    }
}

I tried clearing User defaults from the application. The UI doesn't refresh but I do see UserDefault test New value when printing dictionaryRepresentation. After, restarting the app, the value "Test" is displayed.

If I do it from a test, it doesn't work.

func testExample() throws {
        // UI tests must launch the application that they test.
        let app = XCUIApplication()
        app.launch()

        app.buttons["Change value"].tap()
        
        for e in UserDefaults.standard.dictionaryRepresentation(){
            print("UserDefault2", e.key, e.value)
        }

        app.buttons["Reset storage"].tap()
        
        for e in UserDefaults.standard.dictionaryRepresentation(){
            print("UserDefault3", e.key, e.value)
        }
    }
UserDefault XCTDisableTelemetryLogging 0
UserDefault AddingEmojiKeybordHandled 1
UserDefault NSInterfaceStyle macintosh
UserDefault AKLastIDMSEnvironment 0
UserDefault NSLanguages (
UserDefault PKLogNotificationServiceResponsesKey 0
UserDefault AppleLanguagesDidMigrate 20E247
UserDefault AppleKeyboards (
UserDefault XCTEmitOSLogs 0
UserDefault XCTIDEConnectionTimeout 600
UserDefault AppleKeyboardsExpanded 1
UserDefault AppleLanguagesSchemaVersion 3000
UserDefault XCTTelemetryFlushTimeout 10
UserDefault ApplePasscodeKeyboards (
UserDefault AppleLanguages (
UserDefault AKLastLocale en_US
UserDefault XCUIApplicationCrashReportTimeoutDefault 20
UserDefault2 AppleLanguages (
UserDefault2 XCTDisableTelemetryLogging 0
UserDefault2 PKLogNotificationServiceResponsesKey 0
UserDefault2 AppleKeyboards (
UserDefault2 NSLanguages (
UserDefault2 AKLastIDMSEnvironment 0
UserDefault2 AppleKeyboardsExpanded 1
UserDefault2 ApplePasscodeKeyboards (
UserDefault2 NSInterfaceStyle macintosh
UserDefault2 XCTIDEConnectionTimeout 600
UserDefault2 AppleLanguagesSchemaVersion 3000
UserDefault2 AKLastLocale en_US
UserDefault2 XCUIApplicationCrashReportTimeoutDefault 20
UserDefault2 XCTEmitOSLogs 0
UserDefault2 AppleLanguagesDidMigrate 20E247
UserDefault2 XCTTelemetryFlushTimeout 10
UserDefault2 AddingEmojiKeybordHandled 1
UserDefault3 PKLogNotificationServiceResponsesKey 0
UserDefault3 AKLastIDMSEnvironment 0
UserDefault3 XCTEmitOSLogs 0
UserDefault3 NSInterfaceStyle macintosh
UserDefault3 AppleLanguages (
UserDefault3 AppleLanguagesDidMigrate 20E247
UserDefault3 NSLanguages (
UserDefault3 XCTDisableTelemetryLogging 0
UserDefault3 AddingEmojiKeybordHandled 1
UserDefault3 AppleKeyboardsExpanded 1
UserDefault3 XCTTelemetryFlushTimeout 10
UserDefault3 AppleKeyboards (
UserDefault3 AKLastLocale en_US
UserDefault3 XCTIDEConnectionTimeout 600
UserDefault3 XCUIApplicationCrashReportTimeoutDefault 20
UserDefault3 AppleLanguagesSchemaVersion 3000
UserDefault3 ApplePasscodeKeyboards (

However, pressing the button "Reset storage" refreshes the UI. test video

like image 389
BPDev Avatar asked Oct 24 '25 01:10

BPDev


1 Answers

I had a similar issue where I was UI Testing T&Cs that saves the accepted version in @AppStorage.

I use the function is_tc_accepted() to toggle the display of the T&Cs.

@AppStorage("configuration_tcAcceptedVersion") private var tcVersion: Double = 0.0

// TCs is the class that holds the current T&Cs version. The version number is updated when the content is updated.
// After updating the version, is_tc_accepted will be false so the modal to accept the new T&Cs will appear.
var is_tc_accepted: Bool {
    return tcVersion == TCs.tc_version
}

func TCCloseButtonClicked() {
   tcVersion = TCs.tc_version
}

Let's say I want to test the app's behavior when the TCs.tc_version = 1.2 , I would perform my UI Test providing the following command:

app.launchArguments += ["-configuration_tcAcceptedVersion", "1.2"]
app.launch()

This would result in having the 'configuration_tcAcceptedVersion' parameter set as read-only. The T&Cs appear but the 'TCCloseButtonClicked()' button handler would simply not do anything as the 'configuration_tcAcceptedVersion' variable is read-only.

I'm really glad I found the option to pass a parameter that resets the @AppStorage so I can UI Test launching the application for the first time.

EDIT

I need to set the T&Cs version to 1.2 for all the tests except the one for making sure the modal is displayed at launch. So I wrapped

app.launchArguments += ["-configuration_tcAcceptedVersion", "1.2"]

into a UITest helper that is called everywhere. When the T&Cs version changes, I can update the version in the helper and I'm done.

All right, since the helper is called everywhere

  • How can I test the accepted version is NOT 1.2?
  • How can I ensure configuration_tcAcceptedVersion is NOT read-only?

app.launchArguments is a [String], so I simply remove the arguments I don't want in the test, after calling the helper, and before launching the app.

app.launchArguments.remove(at: 1)
app.launchArguments.remove(at: 0)

That will work only the first time, since the application will remember I accepted the latest T&Cs in the previous UI Test.

For that, I ned to clear the @AppStorage passing an additional argument in the UI Test

app.launchArguments += ["RESET_APPSTORAGE"]

So I catch that argument in the init() function of the root application file

if ProcessInfo.processInfo.arguments.contains("RESET_APPSTORAGE") {
    if let bundleID = Bundle.main.bundleIdentifier {
        UserDefaults.standard.removePersistentDomain(forName: bundleID)
    }
}

Hope that helps!
like image 69
Alexandre Bergia Avatar answered Oct 27 '25 00:10

Alexandre Bergia



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!