#ios #swift #property-wrapper
#iOS #swift #свойство-оболочка
Вопрос:
Допустим, у меня есть очень распространенный вариант использования оболочки свойств UserDefaults
.
@propertyWrapper
struct DefaultsStorage<Value> {
private let key: String
private let storage: UserDefaults
var wrappedValue: Value? {
get {
guard let value = storage.value(forKey: key) as? Value else {
return nil
}
return value
}
nonmutating set {
storage.setValue(newValue, forKey: key)
}
}
init(key: String, storage: UserDefaults = .standard) {
self.key = key
self.storage = storage
}
}
Сейчас я объявляю объект, в котором будут храниться все мои значения UserDefaults
.
struct UserDefaultsStorage {
@DefaultsStorage(key: "userName")
var userName: String?
}
Теперь, когда я хочу использовать его где-нибудь, скажем, в модели представления, у меня было бы что-то вроде этого.
final class ViewModel {
func getUserName() -> String? {
UserDefaultsStorage().userName
}
}
Здесь возникает несколько вопросов.
- Похоже, в этом случае я обязан использовать
.standard
пользовательские значения по умолчанию. Как протестировать эту модель представления, используя другой / смоделированный экземплярUserDefaults
? - Как протестировать эту оболочку свойств, используя другой / mocked экземпляр
UserDefaults
? Должен ли я создавать новый тип, который является чистой копией вышеуказанногоDefaultsStorage
, передавать mockedUserDefaults
и тестировать этот объект?
struct TestUserDefaultsStorage {
@DefaultsStorage(key: "userName", storage: UserDefaults(suiteName: #file)!)
var userName: String?
}
Комментарии:
1. Обычный ключ к издевательству — это протокол…
2. Вы имеете в виду издевательство над всем
UserDefaultsStorage
?3. Я имею в виду издевательство над ошибками пользователя.
4. Извините, но это именно то, чего я не могу достичь.
5. Но вы не хотите издеваться над ошибками пользователя?
Ответ №1:
Как @mat уже упоминалось в комментариях, вам нужна protocol
UserDefaults
зависимость от макета. Что-то вроде этого подойдет:
protocol UserDefaultsStorage {
func value(forKey key: String) -> Any?
func setValue(_ value: Any?, forKey key: String)
}
extension UserDefaults: UserDefaultsStorage {}
Затем вы можете изменить свой DefaultsStorage
propertyWrapper, чтобы использовать UserDefaultsStorage
ссылку вместо UserDefaults
:
@propertyWrapper
struct DefaultsStorage<Value> {
private let key: String
private let storage: UserDefaultsStorage
var wrappedValue: Value? {
get {
return storage.value(forKey: key) as? Value
}
nonmutating set {
storage.setValue(newValue, forKey: key)
}
}
init(key: String, storage: UserDefaultsStorage = UserDefaults.standard) {
self.key = key
self.storage = storage
}
}
После этого макет UserDefaultsStorage
может выглядеть так:
class UserDefaultsStorageMock: UserDefaultsStorage {
var values: [String: Any]
init(values: [String: Any] = [:]) {
self.values = values
}
func value(forKey key: String) -> Any? {
return values[key]
}
func setValue(_ value: Any?, forKey key: String) {
values[key] = value
}
}
И для тестирования DefaultsStorage
передайте экземпляр UserDefaultsStorageMock
в качестве параметра хранилища:
import XCTest
class DefaultsStorageTests: XCTestCase {
class TestUserDefaultsStorage {
@DefaultsStorage(
key: "userName",
storage: UserDefaultsStorageMock(values: ["userName": "TestUsername"])
)
var userName: String?
}
func test_userName() {
let testUserDefaultsStorage = TestUserDefaultsStorage()
XCTAssertEqual(testUserDefaultsStorage.userName, "TestUsername")
}
}
Комментарии:
1. Привет и спасибо за ответ. Это полностью имеет смысл, однако я считаю, что это не отвечает на мой первый вопрос. Как бы вы протестировали модель представления, в которой это хранилище используется без использования
.standard
пользовательских настроек по умолчанию?2. @gasho в моем примере я тестирую
DefaultsStorage
. Чтобы ваша модель представления была тестируемой, вы должны ввести новый протокол, которыйDefaultsStorage
должен соответствовать. Ваша модель представления должна иметь ссылку на этот протокол, аDefaultsStorage
не напрямую, и тогда вы сможете издеватьсяDefaultsStorage
и вводить свой макет экземпляра в свою модель представления. В общем, когда вы хотите что-то протестировать, вы должны иметь возможность имитировать все его зависимости.