使用 XCUIApplication.launchArguments 覆盖 UserDefaults 会忽略该密钥的所有未来更新,那么我该如何测试它?

问题描述 投票:0回答:1

我可以通过使用 XCUIApplication.launchArguments 设置来成功覆盖 UserDefaults 值。但基于 此链接 和我注意到的行为,一旦我覆盖 UserDefaults 值,应用程序尝试对其进行的所有更改都会被忽略

如何为 UserDefaults 键设置初始测试值,但仍允许我的应用程序更改该值?如何正确测试我的应用程序对 UserDefaults 更新的响应并避免必须按顺序运行的不稳定测试?

更多背景/细节: 在我的 iOS 应用程序中,我使用 UserDefaults 来指示如何呈现一些应在应用程序会话中持续存在的内容。示例:我有一个设置,用于跟踪用户是否想要查看一天中的时间与闹钟倒计时器(例如显示“下午 3:00”与“1 小时后”)。让我们用键

showClockTime
将其称为 UserDefaults 中的布尔值,用户可以从设置 UserDefaults 值的“设置”页面更改其首选项,而我的其他 UIViewController 在 viewWillAppear() 中检查 UserDefaults 值,以便我显示正确的内容。

我不是在应用程序中添加相同的检查来判断这个布尔值是否为 nil、true 或 false,而是在 SceneDelegate 中检查它,如果该值为 nil,则将其设置为我喜欢的任何默认值,然后假设它在其余部分中永远不会为 nil我的应用程序。 (我知道如果您使用

false
,布尔值默认返回
UserDefaults.standard.bool("showClockTime")
而不是 nil,但我也想测试一些字符串设置,所以就用它吧。)

我正在尝试编写集成测试来测试:

  • 如果用户从未打开过应用程序,在他们看到我的第一个 UIViewController 之前,我是否在 SceneDelegate 中设置正确的默认值? (这意味着:如果该值最初是
    nil
    ,我想验证我将其设置为
    true
    并且UI呈现为
    true
    设置而不是默认的
    false
    ,否则会是这样)
  • UserDefaults 在测试运行中持续存在,因此 为了获得非片状测试,如何设置测试的初始 UserDefaults 值,同时测试我的“设置”页面是否正确更新该值。例如,我有一个想要切换为“关闭”的开关(因此
    showClockTime = false
    ),并且我需要知道它在测试运行之前以“打开”状态启动(因此
    showClockTime
    必须最初为
    true
    或切换不会达到我的预期)。

不确定这是否重要,但有关我的应用程序的其他信息:

  • 这是一个普通的 Swift 应用程序,我没有使用任何库

尝试将 UserDefaults 键

showClockTime
设置为 nil,因为它将在第一次应用程序启动时出现,并且在测试运行后我无法更改设置。

swift nsuserdefaults xcode-ui-testing
1个回答
0
投票

我找到了解决方法,但我不确定这是最好的方法:

覆盖 UserDefaults 键只会使 that 键在测试的其余部分中不可变,因此需要覆盖测试键,并在 AppDelegate 内部将该覆盖转换为应覆盖的实际键。

在 UI 测试中:

// Assuming you want to override the actual "legitKeyName" in UserDefaults
extension XCUIApplication {
    
    func resetLegitFlag() {
        launchArguments += ["-isTestEnvironment", "true"]
        launchArguments += ["-testlegitKeyName", "nil"]
    }
    
}

然后在AppDelegate中:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    if (UserDefaults.standard.bool(forKey: "isTestEnvironment")) {
        // We are in a test, check if we need to override a key
        let actualKeyName = "legitKeyName"
        let overrideValue = UserDefaults.standard.string(forKey: "test" + actualKeyName)
        
        if (overrideValue != nil) {
            switch overrideValue {
            case "nil":
                UserDefaults.standard.removeObject(forKey: actualKeyName)
                break
            default:
                UserDefaults.standard.set(overrideValue, forKey: actualKeyName)
            }
        }
    }

}

您可以对每个想要覆盖但仍会在测试中更改的标志执行此操作。

然后在你的测试中:

final class LegitKeyUITests: XCTestCase {    
    
    override func setUpWithError() throws {
        let app = XCUIApplication()
        app.resetLegitFlag()
        app.launch()
        ...
    }
}

它适用于我的情况,但我不喜欢测试代码位于 AppDelegate 内部。我试图通过添加指定它是测试的第二个标志来降低在产品中意外被调用并更改真实用户设置的风险。

© www.soinside.com 2019 - 2024. All rights reserved.