В настоящее время я работаю над тестированием своего приложения, и у меня возникла проблема с издевательством над пользовательскими настройками по умолчанию. Позвольте мне сначала показать вам мою установку:
вот как я издеваюсь над пользовательскими настройками по умолчанию:
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 под названием favouriteMovie, которую я установил следующим образом:
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")
перед запуском теста, но проблема в том, что переменная favouriteMovie всегда возвращает 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) Выполняя 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
}
}
Хорошо, тогда обновите свой вопрос, указав протокол и расширение, чтобы лучше понять.
@ Kilom94l попробуйте сейчас, так как я обновил ответ в соответствии с кодом, которым вы поделились.
спасибо большое, это сработало. Если у вас есть время, не могли бы вы мне это объяснить?
Да, на самом деле в userDefaults вы всегда возвращали новый объект MockUserDefaults, что означает, что у него будут пустые данные. Объект MockUserDefaults отличался при сохранении и получении значения favs. В своем решении я создаю MockUserDefaults только один раз, когда он равен нулю, в противном случае я возвращаю тот же объект. Вот почему ваше значение favs извлекается правильно, поскольку это тот же объект, который вы использовали во время сохранения.
понятно. Спасибо за ваше время :)
спасибо за ответ, но я не знаю, видели ли вы мой отредактированный вопрос или нет, но, как я уже сказал, userDefaults - это расширение протокола, которому я сделал все контроллеры uiview, соответствующие ему, поэтому я могу получить доступ к этой переменной во всех их, но когда я перейду к вашему второму предложению и добавлю _mockDefaults к расширению, я получаю эту ошибку «Расширения не должны содержать сохраненные свойства», что вы думаете?