iOS - 在加载视图控制器之前模拟UserDefault

时间:2018-05-07 11:01:47

标签: ios iphone swift unit-testing testing

我目前正在为我的应用程序进行测试,并且在模拟用户默认值时遇到了问题。让我先向您展示我的设置:

这是我模拟用户默认值的方式:

class MockUserDefaults: UserDefaults {

    typealias FakeData = Dictionary<String, Any?>
    var data: FakeData

    convenience init() {
        self.init(suiteName: "mocking")!
    }

    override init?(suiteName suitename: String?) {
        data = FakeDefaults()
        UserDefaults().removePersistentDomain(forName: suitename!)
        super.init(suiteName: suitename)
    }

    override func object(forKey defaultName: String) -> Any? {
        if let data = data[defaultName] {
            return data
        }
        return nil
    }

    override func set(_ value: Any?, forKey defaultName: String) {
        if defaultName == "favs"{
            data[defaultName] = value
        }

    }

}

我的视图控制器中有一个名为userDefaults的变量,我设置如下:

var userDefaults : UserDefaults {
        if (NSClassFromString("XCTest") != nil) {
            return MockUserDefaults()
        }
        return UserDefaults.standard
    }

这个变量实际上是一个协议的扩展,uiviewcontroller使它符合它,以确保我的所有视图控制器都有这个变量。

我在myViewcontroller中也有一个名为favoriteMovie的变量,我设置如下:

private var favoriteMovie: Favorite? {
    if let favoriteString = userDefaults.value(forKey: "favs") as? String {
        return favorites.first(where: {$0.name == favoriteString})
    }
    return nil   
}

现在问题出现了,当我去尝试测试这个视图控制器时,我需要设置userDefault一个对象,例如:

    myviewController.userDefaults.set("avengers", forKey: "favs")

在测试运行之前,但问题是favoriteMovie变量总是返回nil,我需要它在测试运行之前返回一个对象。任何帮助。提前谢谢。

更新:

这是协议:

protocol Mockable: class {
    var userDefaults: UserDefaults { get }
}

这是扩展名:

extension UIViewController: Mockable {}
extension Mockable {
        var userDefaults : UserDefaults {
            if (NSClassFromString("XCTest") != nil) {
                return MockUserDefaults()
            }
            return UserDefaults.standard
        }
}

1 个答案:

答案 0 :(得分:0)

以下是两种解决方法。

1)通过做一些DI。在您viewController中将userDefaults声明为非计算属性,如下所示

var userDefaults : UserDefaults?

在您的测试用例中,创建MockUserDefaults对象,设置值并在启动时将其分配给viewController,如下所示

let mockUD = MockUserDefaults()
mockUD.set("avengers", forKey: "favs")

myviewController.userDefaults = mockUD

现在您将获得avengers对象。

2)随着问题的更新,以下是保存mockDefaults对象的修复方法,

struct AssociatedMock {
    static var key: UInt8 = 0
}

extension Mockable {

    private (set) var _mockDefaults: MockUserDefaults? {
        get {
            guard let value = objc_getAssociatedObject(self, &AssociatedMock.key) as? MockUserDefaults else {
                return nil
            }
            return value
        }
        set(newValue) {
            objc_setAssociatedObject(self, &AssociatedMock.key, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    var userDefaults : UserDefaults {
        if (NSClassFromString("XCTest") != nil) {
            if self._mockDefaults == nil {
                self._mockDefaults = MockUserDefaults()
            }
            return self._mockDefaults!
        }
        return UserDefaults.standard
    }
}