swiftxcode-ui-testing

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


Solution

  • The UI test is running in a separate process, so it is accessing a different UserDefaults. When starting a UI test, there is a new app icon "TestStorageUITests" that doesn't do anything, but the test still runs in the actual application "TestStorage". (I am guessing that a test starts with "TestStorageUITests" and switches to "TestStorage" when executing app.launch().)

    It would have been nice to be able to access the application data in the setup code at least, but one approach would be to send flags when launching the application and call removePersistentDomainForName from the App.

    let app = XCUIApplication()
    app.launchArguments += ["UI-Testing"]
    app.launch()
    
    @main
    struct SomeApp: App {
        
        init(){
            if ProcessInfo.processInfo.arguments.contains("UI-Testing"){
                if let bundleID = Bundle.main.bundleIdentifier {
                    UserDefaults.standard.removePersistentDomain(forName: bundleID)
                }
            }
        }
        
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }