How to test and mock property wrappers in Swift? - ios

Let's say I have a very common use case for a property wrapper using 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
}
}
I am now declaring an object that would hold all my values stored in UserDefaults.
struct UserDefaultsStorage {
#DefaultsStorage(key: "userName")
var userName: String?
}
Now when I want to use it somewhere, let's say in a view model, I would have something like this.
final class ViewModel {
func getUserName() -> String? {
UserDefaultsStorage().userName
}
}
Few questions arise here.
It seems that I am obliged to use .standard user defaults in this case. How to test that view model using other/mocked instance of UserDefaults?
How to test that property wrapper using other/mocked instance of UserDefaults? Do I have to create a new type that is a clean copy of the above's DefaultsStorage, pass mocked UserDefaults and test that object?
struct TestUserDefaultsStorage {
#DefaultsStorage(key: "userName", storage: UserDefaults(suiteName: #file)!)
var userName: String?
}

As #mat already mentioned in the comments, you need a protocol to mock UserDefaults dependency. Something like this will do:
protocol UserDefaultsStorage {
func value(forKey key: String) -> Any?
func setValue(_ value: Any?, forKey key: String)
}
extension UserDefaults: UserDefaultsStorage {}
Then you can change your DefaultsStorage propertyWrapper to use a UserDefaultsStorage reference instead of 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
}
}
After that a mock UserDefaultsStorage might look like this:
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
}
}
And to test DefaultsStorage, pass an instance of UserDefaultsStorageMock as its storage parameter:
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")
}
}

This might not be the best solution, however, I haven't figured out a way to inject UserDefaults that use property wrappers into a ViewModel. If there is such an option, then gcharita's proposal to use another protocol would be a good one to implement.
I used the same UserDefaults in the test class as in the ViewModel. I save the original values before each test and restore them after each test.
class ViewModelTests: XCTestCase {
private lazy var userDefaults = newUserDefaults()
private var preTestsInitialValues: PreTestsInitialValues!
override func setUpWithError() throws {
savePreTestUserDefaults()
}
override func tearDownWithError() throws {
restoreUserDefaults()
}
private func newUserDefaults() -> UserDefaults.Type {
return UserDefaults.self
}
private func savePreTestUserDefaults() {
preTestsInitialValues = PreTestsInitialValues(userName: userDefaults.userName)
}
private func restoreUserDefaults() {
userDefaults.userName = preTestsInitialValues.userName
}
func testUsername() throws {
//"inject" User Defaults with the desired values
let username = "No one"
userDefaults.userName = username
let viewModel = ViewModel()
let usernameFromViewModel = viewModel.getUserName()
XCTAssertEqual(username, usernameFromViewModel)
}
}
struct PreTestsInitialValues {
let userName: String?
}

Related

iOS - Mocking UserDefault before loading the view controller

I am currently working on test for my application and I have faced a problem when mocking user defaults. Let me first show you my setup :
this is how I mock user Defaults :
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
}
}
}
I have a variable in my view controller called : userDefaults, and I set it like this :
var userDefaults : UserDefaults {
if (NSClassFromString("XCTest") != nil) {
return MockUserDefaults()
}
return UserDefaults.standard
}
this variable is actually an extension to a protocol which a made uiviewcontroller conform to it to make sure all my view controllers have this variable.
I also have a variable in myViewcontroller called favoriteMovie which I set like this :
private var favoriteMovie: Favorite? {
if let favoriteString = userDefaults.value(forKey: "favs") as? String {
return favorites.first(where: {$0.name == favoriteString})
}
return nil
}
now here's where the problem is, when I go and try to test this view controller , I need to set userDefault with an object for example :
myviewController.userDefaults.set("avengers", forKey: "favs")
before the test runs, but the problem is that favoriteMovie variable always return nil and I need it to return an object before the test runs . Any help. Thanks in advance.
UPDATE :
this is the protocol :
protocol Mockable: class {
var userDefaults: UserDefaults { get }
}
this is the extension :
extension UIViewController: Mockable {}
extension Mockable {
var userDefaults : UserDefaults {
if (NSClassFromString("XCTest") != nil) {
return MockUserDefaults()
}
return UserDefaults.standard
}
}
Here are two ways to fix it.
1) By doing some DI. In you viewController declare userDefaults as non-computed property as below
var userDefaults : UserDefaults?
In your test case, create MockUserDefaults object, set values and assign it to viewController when you are initiating it as below,
let mockUD = MockUserDefaults()
mockUD.set("avengers", forKey: "favs")
myviewController.userDefaults = mockUD
Now you will get the avengers object.
2) As the question is updated, here is the fix to hold the mockDefaults object,
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
}
}

Using enums in a class as a parameter

Basically I have a DataManager singleton as follows:
class DataManager {
static let shared = DataManager()
private init() {}
static func set(_ object: String, forKey defaultName: String) {
let defaults: UserDefaults = UserDefaults.standard
defaults.set(object, forKey:defaultName)
defaults.synchronize()
}
static func object(forKey key: String) -> String {
let defaults: UserDefaults = UserDefaults.standard
return defaults.object(forKey: key) as! String
}
}
What I want to achieve it as follows, when setting or retrieving saved objects:
DataManager.set("some hash", forKey: .hash)
instead of:
DataManager.set("some hash", forKey: "hash")
Also, what's the technical term for the functionality I'm looking for?
You can have an enum, simply DefaultKey
enum DefaultKey: String {
case hash = "hash"
case secondKey = "secondKey"
case thirdKey = "thirdKey"
}
Now what you have to do is
static func set(_ object: String, forKey keyType: DefaultKey) {
let defaults: UserDefaults = UserDefaults.standard
let key = keyType.rawValue // rawvalue for keyType .hash will actually return you "hash".
defaults.set(object, forKey: key)
defaults.synchronize()
}
Now you will be able to call function like
DataManager.set("some hash", forKey: .hash)
Yes, you can use enums, for example:
enum DataManagerKey: String {
case hash
}
and then use it in set method:
static func set(_ object: String, forKey defaultName: DataManagerKey)
You could simply achieve it by implementing an enum and let defaultName parameter data type to be of the implemented enum, as follows:
enum DataManagerDefaultName: String {
case hash
// other cases goes here
}
Usage:
class DataManager {
static let shared = DataManager()
private init() {
}
static func set(_ object: String, forKey defaultName: String) { {
let defaults: UserDefaults = UserDefaults.standard
defaults.set(object, forKey:defaultName.rawValue)
defaults.synchronize()
}
static func object(forKey key: DataManagerDefaultName) -> String {
let defaults: UserDefaults = UserDefaults.standard
return defaults.object(forKey: key) as! String
}
}
Executing:
// executing:
// setting the value:
DataManager.set("my value", forKey: .hash)
// getting the value:
let myValue = DataManager.object(forKey: .hash) // "my value"
Note that you'd be able to not only set the value by using the enum, also getting it.
I find the most clean approach to be defining an enum inside the class, like this:
class DataManager {
enum Key: String {
case hash
}
static let shared = DataManager()
private init() {
}
static func set(_ object: String, forKey defaultName: DataManager.Key) {
let defaults: UserDefaults = UserDefaults.standard
defaults.set(object, forKey:defaultName.rawValue)
defaults.synchronize()
}
static func object(forKey key: DataManager.Key) -> String {
let defaults: UserDefaults = UserDefaults.standard
return defaults.object(forKey: key.rawValue) as! String
}
}
Usage stays the same as if it were defined outside, since Swift will infer the DataManager.Key prefix:
DataManager.set("some hash", forKey: .hash)

EVReflection + Moya + Realm + RxSwift - Could not create an instance for type dict

I'm stuck putting all of the above together. I'll appreciate if I can get any input.
Here's my short setup:
typealias RealmObject = Object
/// Extension to ignore undefined keys when mapping
extension RealmObject : EVReflectable {
open override func setValue(_ value: Any?, forUndefinedKey key: String) { }
}
Sample Realm models:
class Product: RealmObject {
dynamic var productId: String = ""
let productLanguages = List<ProductLanguage>()
override static func primaryKey() -> String? {
return "productId"
}
}
class ProductLanguage: RealmObject {
dynamic var productLanguageId: String = ""
dynamic var languageCode: String = ""
dynamic var productName: String = ""
override static func primaryKey() -> String? {
return "productLanguageId"
}
}
To fetch product details I use Moya and RxSwift:
func getProduct(productItemKey: String) -> Observable<Product> {
return provider.request(.product(productId: productItemKey)).map(to: Product.self)
}
I think .map(to: Product.self) does not work with realm Lists out of the box. For each object inside the list I get an error:
ERROR: Could not create an instance for type
dict:{
CreateDate = "2015-10-12T11:11:50.013Z";
IngredientList = "Acao ingredient";
LanguageCode = "en-US";
ProductId = "d6bb0084-6838-11e5-9225-00ac14ef2300";
ProductLanguageId = "f96848d0-df77-4594-99b7-d390bb127891";
ProductName = Acao;
Tagline = "The smart drink - 100% organic, vegan energy booster with guara"
}
Is there any other way to map Moya response into Realm objects?
Thank you very much for any input!
Turns out it was a bug in EVReflection. Fixed in 4.17.0

How can I create a base from this Swift class so that it can be inherited?

I have a class named UserManager.
public class UserManager{
static let sharedInstance = UserManager()
let center = NSNotificationCenter.defaultCenter()
let queue = NSOperationQueue.mainQueue()
var resources = Dictionary<Int, User>()
var clients = Dictionary<Int, Set<String>>()
private init(){
}
private func addToClientMap(id: Int, clientName: String){
if clients[id] == nil {
clients[id] = Set<String>()
clients[id]!.insert(clientName)
}else{
clients[id]!.insert(clientName)
}
}
func getResource(id: Int, clientName: String) -> User?{
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
return nil
}
}
func createResource(data:JSON, clientName: String) -> User? {
if let id = data["id"].int {
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
resources[id] = mapJSONToUser(data) //need to make generic
addToClientMap(id, clientName: clientName)
return resources[id]
}
}
return nil
}
func releaseResource(id: Int, clientName: String){
if clients[id] != nil {
clients[id]!.remove(clientName)
if clients[id]!.count == 0 {
resources.removeValueForKey(id)
clients.removeValueForKey(id)
}
}
}
}
Notice that I have an object called User, and it's used everywhere in this class.
I'd like to have classes called PostManager and AdminManager, which uses the same logic as the class above.
I could simply copy and paste the code above and replace the object User with Post and Admin. But...obviously this is bad practice.
What can I do to this class so that it accepts any resource? Not just User
The most obvious way to do something like this is to embed all of the generic functionality in a generic class, then inherit your UserManager from that:
protocol Managable {
init(json:JSON)
}
public class Manager<T:Manageable> {
let center = NSNotificationCenter.defaultCenter()
let queue = NSOperationQueue.mainQueue()
var resources = Dictionary<Int, T>()
var clients = Dictionary<Int, Set<String>>()
private init(){
}
private func addToClientMap(id: Int, clientName: String){
if clients[id] == nil {
clients[id] = Set<String>()
clients[id]!.insert(clientName)
}else{
clients[id]!.insert(clientName)
}
}
func getResource(id: Int, clientName: String) -> T?{
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
return nil
}
}
func createResource(data:JSON, clientName: String) -> T? {
if let id = data["id"].int {
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
resources[id] = T(json:data) //need to make generic
addToClientMap(id, clientName: clientName)
return resources[id]
}
}
return nil
}
func releaseResource(id: Int, clientName: String){
if clients[id] != nil {
clients[id]!.remove(clientName)
if clients[id]!.count == 0 {
resources.removeValueForKey(id)
clients.removeValueForKey(id)
}
}
}
}
class User : Managable {
required init(json:JSON) {
}
}
class UserManager : Manager<User> {
static var instance = UserManager()
}
Now then, any class that implements the Manageable protocol (ie., it has an init(json:JSON) method can have a Manager class variant. Note that since a generic class can't have a static property, that's been moved into the subclass.
Given that inheritance can hide implementation details, if reference semantics are not needed then a protocol + associated type (generics) implementation using structs might be safer and arguably more "Swifty".
Define your protocol with an associated type (Swift 2.2) or type alias (Swift 2.1):
protocol Manager {
associatedtype MyManagedObject // use typealias instead for Swift 2.1
func getResource(id: Int, clientName: String) -> MyManagedObject?
func createResource(data: JSON, clientName: String) -> MyManagedObject?
func releaseResource(id: Int, clientName: String)
}
And then your implementation becomes:
public struct UserManager: Manager {
typealias MyManagedObject = User
func getResource(id: Int, clientName: String) -> User? { ... }
func createResource(data: JSON, clientName: String) -> User? { ... }
func releaseResource(id: Int, clientName: String) { ... }
}
And you can further add objects using the same protocol easily, specifying what 'MyManagedObject' should be:
public struct PostManager: Manager {
typealias MyManagedObject = Post
func getResource(id: Int, clientName: String) -> Post? { ... }
func createResource(data: JSON, clientName: String) -> Post? { ... }
func releaseResource(id: Int, clientName: String) { ... }
}
I would recommend reading up more on protocols and generics in detail (there are many examples online, Apple's documentation is a good place to start).

How to create custom initilizers with Cocoa Realm

I have a swift project with Realm in it where I would like to store objects using RLMObject, however I cannot create a custom init, as it will throw an error.
I Have tried
import Foundation
import Realm
class Foo: RLMObject {
dynamic var First: String = ""
dynamic var Last: String = ""
init(first: String, last: String) {
super.init()
self.First = first
self.Last = last
}
override init(object: AnyObject?) {
super.init(object:object)
}
override init(object value: AnyObject!, schema: RLMSchema!) {
super.init(object: value, schema: schema)
}
override init(objectSchema: RLMObjectSchema) {
super.init(objectSchema: objectSchema)
}
}
Which was suggested here

Resources