简体   繁体   English

如何使用模拟器在XCUITest中以编程方式清除NSUserDefaults

[英]How to clear NSUserDefaults programmatically in XCUITest using Simulator

I've read several answers related to this and they suggest doing one of the following, but these options are not working for me. 我已经阅读了几个与此相关的答案,他们建议执行以下操作之一,但这些选项对我不起作用。 I have an XCUITest and I'm trying to clear the standard user defaults before before running the rest of my XCUITest. 我有一个XCUITest,我试图在运行其余的XCUITest之前清除标准用户默认值。 Currently my test app has a button that calls this code. 目前我的测试应用程序有一个调用此代码的按钮。 I've also tried calling this code directly from within the XCUITest (I'm not sure if this is expected to work or if it needs to be run from within the app). 我也尝试直接从XCUITest中调用此代码(我不确定这是否可以使用或者是否需要在应用程序内运行)。

NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];

I've also tried removing each individually: 我也试过逐个删除:

[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"MyKey1"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"MyKey2"];

I also tried each of the above methods followed by a synchronize call: 我还尝试了上面的每个方法,然后是同步调用:

[[NSUserDefaults standardUserDefaults] synchronize];

The next time I read @"MyKey1" from the NSUserDefaults its still has the old value and has not been deleted. 下次我从NSUserDefaults读取@“MyKey1”时,它仍然具有旧值并且尚未被删除。

Is there any way to remove an object from the NSUserDefaults programmatically when running an XCUITest in the simulator? 在模拟器中运行XCUITest时,有没有办法以编程方式从NSUserDefaults中删除对象? These are automated tests, so I can't always manually click on "Reset Contents and Settings" in xcode. 这些都是自动化测试,所以我不能总是手动点击xcode中的“重置内容和设置”。

Thanks! 谢谢!

There are couple of ways to solve your issues by setting the UserDefaults values before your tests run the app (not after). 通过在测试运行应用程序之前设置UserDefaults值(而不是之后),有几种方法可以解决您的问题。

Solution #1 解决方案#1

Mock UserDefaults values for certain keys using launchArguments : 使用某些键模拟UserDefaults值launchArguments

func testExample() {
    let app = XCUIApplication()
    app.launchArguments += ["-keepScreenOnKey", "YES"]
    app.launch()
}

Note the minus sign before the keepScreenOnKey key. 请注意 keepScreenOnKey键之前的keepScreenOnKey The minus sign indicates that it should take the next launch argument value as a value for that UserDefaults key. 减号表示它应该将下一个启动参数值作为该UserDefaults键的值。

Solution #2 (if solution #1 doesn't work) 解决方案#2(如果解决方案#1不起作用)

The previous solution might not work if you're using the SwiftyUserDefaults library. 如果您使用的是SwiftyUserDefaults库,则以前的解决方案可能无效。 In this case, there is one elegant solution: if the application is running under UI tests, erase all UserDefaults data from the app. 在这种情况下,有一个优雅的解决方案:如果应用程序在UI测试下运行,则从应用程序中清除所有UserDefaults数据。 How does the app know if it is being run under UI tests? 应用程序如何知道它是否在UI测试下运行? By passing the launch arguments message, like this: 通过传递启动参数消息,如下所示:

override func setUp() {
    super.setUp()
    app.launchArguments += ["UI-Testing"]
}

Then, in AppDelegate.swift check if the app is running under UI-Testing and remove all UserDefaults data (like you do): 然后,在AppDelegate.swift检查应用程序是否在UI-Testing下运行并删除所有UserDefaults数据(就像您一样):

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        setStateForUITesting()

        return true
    }

    static var isUITestingEnabled: Bool {
        get {
            return ProcessInfo.processInfo.arguments.contains("UI-Testing")
        }
    }

    private func setStateForUITesting() {
        if AppDelegate.isUITestingEnabled {
            UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
        }
    }
}

Solution #3 (if solution #2 is not enough) 解决方案#3(如果解决方案#2不够)

But what if the UI test expects states other than the default state set by solution #2? 但是,如果UI测试需要解决方案#2设置的默认状态以外的状态怎么办? Bools default to false , Integers to 0 and Strings to "" , but what if you need true for the Bool key? Bools默认为false ,Integers为0 ,字符串为"" ,但如果Bool键需要true ,该怎么办?

Do something like this: 做这样的事情:

func testExample() {
    app.launchEnvironment["UI-TestingKey_keepScreenOn"] = "YES"
    app.launch()
}

And then in AppDelegate.swift file: 然后在AppDelegate.swift文件中:

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    static let uiTestingKeyPrefix = "UI-TestingKey_"

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        if AppDelegate.isUITestingEnabled {
            setUserDefaults()
        }

        return true
    }

    static var isUITestingEnabled: Bool {
        get {
            return ProcessInfo.processInfo.arguments.contains("UI-Testing")
        }
    }

    private func setUserDefaults() {
        for (key, value)
            in ProcessInfo.processInfo.environment
            where key.hasPrefix(AppDelegate.uiTestingKeyPrefix) {
            // Truncate "UI-TestingKey_" part
            let userDefaultsKey = key.truncateUITestingKey()
            switch value {
            case "YES":
                UserDefaults.standard.set(true, forKey: userDefaultsKey)
            case "NO":
                UserDefaults.standard.set(false, forKey: userDefaultsKey)
            default:
                UserDefaults.standard.set(value, forKey: userDefaultsKey)
            }
        }
    }
}

extension String {
    func truncateUITestingKey() -> String {
        if let range = self.range(of: AppDelegate.uiTestingKeyPrefix) {
            let userDefaultsKey = self[range.upperBound...]
            return String(userDefaultsKey)
        }
        return self
    }
}

Please note that this example only works for Bool and String keys. 请注意,此示例仅适用于Bool和String键。 If you need more scalability, the switch command should be modified to somehow check if the value is Integer or Double or Any other value, but the general idea is here. 如果你需要更多的可伸缩性,应该修改switch命令以某种方式检查值是Integer还是Double或Any其他值,但总体思路就在这里。

EDIT: It looks like the reason for using solutions #2 and #3 is not valid anymore as of SwiftyUserDefaults version 4.0.0-beta.1 as they've added support for setting values through launch arguments. 编辑:从SwiftyUserDefaults 版本4.0.0-beta.1开始 ,使用解决方案#2和#3的原因似乎不再有效,因为他们通过启动参数添加了对设置值的支持。 But, I have to admit that I have not tested SwiftyUserDefaults library from this version onward, so I'll keep both solutions here. 但是,我不得不承认我之前没有测试过这个版本的SwiftyUserDefaults库,所以我会在这里保留两个解决方案。

With UI tests, the app runs as a separate process. 通过UI测试,应用程序作为单独的进程运行。 You would need to call the methods to clear NSUserDefaults from within the app itself. 您需要调用方法从应用程序本身清除NSUserDefaults。

We have our UI Test pass a resetNSUserDefaults flag to the app when it launches the app. 我们的UI测试在启动应用程序时向应用程序传递了res​​etNSUserDefaults标志。 The app then clears the NSUserDefaults early in the launch process. 然后,应用程序会在启动过程的早期清除NSUserDefaults。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM