How to mock and test a UserDefaults computed property that store String? - swift

I am trying to mock UserDefaults to be able to test its behaviour.
What is the best way to do it without corrupting the real computed property that save the user token key?
class UserDefaultsService {
private struct Keys {
static let token = "partageTokenKey"
}
//MARK: - Save or retrieve the user token
static var token: String? {
get {
return UserDefaults.standard.string(
forKey: Keys.token)
}
set {
UserDefaults.standard.set(
newValue, forKey: Keys.token)
}
}
}

You can subclass UserDefaults :
(source)
class MockUserDefaults : UserDefaults {
convenience init() {
self.init(suiteName: "Mock User Defaults")!
}
override init?(suiteName suitename: String?) {
UserDefaults().removePersistentDomain(forName: suitename!)
super.init(suiteName: suitename)
}
}
And in UserDefaultsService instead of directly accessing UserDefaults.standard you can create property based on the target that you are running. In production/staging you can have UserDefaults.standard and for testing you can have MockUserDefaults
You should add PREPROCESSOR Flag before using them
#if TESTING
let userDefaults: UserDefaults = UserDefaults.standard
#else
let userDefaults: UserDefaults = MockUserDefaults(suiteName: "testing") ?? UserDefaults.standard
#endif

One way of doing it is to wrap your UserDefaults in a protocol and expose what you need.
Then you create a an actual class which conforms to that protocol and which uses UserDefaults
You can then instantiate your UserDefaultsService with that class.
When you need to test, you can create a mock conforming to the same protocol and use that instead. That way you won't "pollute" your UserDefaults.
The above might seem like a bit of a mouthful so lets break it down.
Note that in the above I removed the "static" part as well, it didn't seem necessary, and it made it easier without it, hope that is OK
1. Create a Protocol
This should be all you are interested in exposing
protocol SettingsContainer {
var token: String? { get set }
}
2. Create an Actual Class
This class will be used with UserDefaults but it is "hidden" behind the protocol.
class UserDefaultsContainer {
private struct Keys {
static let token = "partageTokenKey"
}
}
extension UserDefaultsContainer: SettingsContainer {
var token: String? {
get {
return UserDefaults.standard.string(forKey: Keys.token)
}
set {
UserDefaults.standard.set(newValue, forKey: Keys.token)
}
}
}
3. Instantiate UserDefaultsService With That Class
Now we create an instance of your UserDefaultsService which has an object conforming to the SettingsContainer protocol.
The beauty is that you can change the provided class later on...for instance when testing.
The UserDefaultsService does not know - or care - whatever the SettingsContainer actually does with the value, as long as it can give and take a token, then the UserDefaultsService is happy.
Here's how that looks, note that we are passing a default parameter, so we don't even have to pass a SettingsContainer unless we have to.
class UserDefaultsService {
private var settingsContainer: SettingsContainer
init(settingsContainer: SettingsContainer = UserDefaultsContainer()) {
self.settingsContainer = settingsContainer
}
var token: String? {
get {
return settingsContainer.token
}
set {
settingsContainer.token = newValue
}
}
}
You can now use a new UserDefaultsService like so:
let userDefaultsService = UserDefaultsService()
print("token: \(userDefaultsService.token)")
4 Testing
"Finally" you say :)
To test the above, you can create a MockSettingsContainer conforming to the SettingsContainer
class MockSettingsContainer: SettingsContainer {
var token: String?
}
and pass that to a new UserDefaultsService instance in your test target.
let mockSettingsContainer = MockSettingsContainer()
let userDefaultsService = UserDefaultsService(settingsContainer: mockSettingsContainer)
And can now test that your UserDefaultsService can actually save and retrieve data without polluting UserDefaults.
Final Notes
The above might seem like a lot of work, but the important thing to understand is:
wrap 3rd party components (like UserDefaults) behind a protocol so you are free to change them later on if so needed (for instance when testing).
Have dependencies in your classes that uses these protocols instead of "real" classes, that way you - again - are free to change the classes. As long as they conform to the protocol, all is well :)
Hope that helps.

A very good solution is to not bother creating a mock or extracting a protocol. Instead init a UserDefaults object in your tests like this:
let userDefaults = UserDefaults(suiteName: #file)
userDefaults.removePersistentDomain(forName: #file)
Now you can go ahead and use the UserDefaults keys you already have defined in an extension and even inject this into any functions as needed! Cool. This will prevent your actual UserDefaults from being touched.
Brief article here

Instead of mocking UserDefaults, you should check the value you persist in UserDefaults, should get retrieved from the same user defaults by accessing using same key. the test case should look like this.
func testWhenTokenIsSavedInUserDefaults_ReturnSameTokenAndVerify {
let persistenceService = UserDefaultsService()
persistenceService.token = "ABC-123"
let defaults = UserDefaults.standard
let expectedValue = defaults.value(forKey: "partageTokenKey")
XCTAssertEquals(persistenceService.token, expectedValue)
}

Related

Subclassing UserDefaults

tldr; why do we always use UserDefaults.standard instead of subclassing UserDefaults to make something that more precisely fits our needs?
Has anyone out there subclassed UserDefaults before? Or is that considered bad practice?
Say, for example, that we make a ColorDefaults subclass of UserDefaults. When the app, the ColorDefaults object is instantiated, and that object loads all its own data. And the loaded data can then by sent to an appropriate object via delegation, or made universally available via a singleton.
My running theory is that UserDefaults is only meant to store relatively amounts of data, so having to use a singleton enforces that idea.
Bottom line: do we use UserDefaults.standard because:
subclassing is frowned upon
we're supposed to avoid saving too much data to UserDefaults in general
there's just not much value in subclassing anyway?
pretty much anything else.
Your ColorDefaults should not be a subclass of UserDefaults. It should be a plain struct or class with computed properties that are backed by UserDefaults.
Here is an example using static properties but you could refactor this to use a singleton class instead.
struct ColorDefaults {
static var someDefault: String {
get {
return UserDefaults.standard.string(forKey: "someKey") ?? "some initial value"
}
set {
UserDefaults.standard.set(newValue, forKey: "someKey")
}
}
}
let someVal = ColorDefaults.someDefault // read
ColorDefaults.someDefault = "hello" // write
This would also be useful if one of your defaults was more complicated and needed to be encoded/decoded for UserDefaults. The logic goes in here and not all over your app.
Note that such a class should only be used to store small bits of preferences, not full blown app data.
User defaults are a system of storage on file. There is little sense in subclassing unless you want to change some of its logic. But you can create multiple suits like UserDefaults(suiteName: String). What do you expect you would do with subclassing? You could simply just globally define let myDefaults = UserDefaults(suiteName: String) and use it anywhere. I guess you could use methods like
class MyDefaults: UserDefaults {
func saveName(_ name: String) {
setValue(name, forKey: "name_key")
}
}
But then again it might make more sense to just create an extension
extension UserDefaults {
func saveName(_ name: String) {
setValue(name, forKey: "name_key")
}
}
Or make it a bit more complex:
extension UserDefaults {
struct User {
static let defaults = UserDefaults(suiteName: "User")
static func saveName(_ name: String) {
defaults.setValue(name, forKey: "name")
}
}
struct General {
static let defaults = UserDefaults.standard
static func saveLastOpened(date: Date) {
defaults.setValue(date, forKey: "last_opened")
}
}
}
But all of these have one fatal flow: Now you are dependent on using user defaults within the app. At some point you may find the need to rather save these data in some other form like a local JSON file synced with iCloud. I guess UserDefaults.User could be modified to do so but would be very ugly. What we want is not UserDefaults.User.saveName("My name") but User.saveName("My name"). From the interface perspective we do not care where this user name is saved and if a new system is introduced to save these data we don't want the change in interface.
In other words, imagine you are using UserDefaults.User.saveName on 100 places in your application and now want to use another system for saving user name. You will now need to change your code on 100 places to use AnotherSystem.User.saveName while if you simply use User.saveName the interface is still valid.
So the bottom line is there is no sense in (extensively) modifying, extending or subclassing UserDefaults because it is better creating a system that wraps UserDefaults and may later be changed to any other system.
Seems you are looking for something like this
class ColorDefaults : NSObject
{
/// Save Data
class func saveDataInDefaultForKey(_ key: String, _ data: Any){
UserDefaults.standard.set(data, forKey: key)
}
/// Retrieve data
class func retrieveDataFromDefaultsWithKey(_ key: String) -> Any {
return UserDefaults.standard.value(forKey: key) as Any
}
}
Save and get data:
/// Save Data
ColorDefaults.saveDataInDefaultForKey("myArray", myArray)
ColorDefaults.saveDataInDefaultForKey("myString", myString)
/// Get Data
if let valueString = ColorDefaults.retrieveDataFromDefaultsWithKey("myString") as? String {
print("Saved Value String: \(valueString)")
}
else {
print("Error retrieving myString")
}
if let valueArray = ColorDefaults.retrieveDataFromDefaultsWithKey("myArray") as? [String] {
print("Saved Value Array: \(valueArray)")
}
else{
print("Error retrieving myArray")
}
Output:

In Swift, how can you call a function immediately after object creation

I have some objects, which are structs, that I initialize from JSON dictionaries ([String : Any]) via an init function provided from an extension on the Decodable protocol (see Init an object conforming to Codable with a dictionary/array).
So basically, I have objects that look like this:
struct ObjectModelA: Codable {
var stringVal: String
var intVal: Int
var boolVal: Bool
// Coding keys omitted for brevity
}
struct ObjectModelB: Codable {
var doubleVal: Double
var arrayOfObjectModelAVal: [ObjectModelA]
// Coding keys omitted for brevity
var complicatedComputedVar: String {
// expensive operations using the values in arrayOfObjectModelAVal
}
}
ObjectModelB contains an array of ObjectModelA, and it also has a property which I only really want to set if the arrayOfObjectModelAVal changes.
I can use a didSet on arrayOfObjectModelAVal, but that only catches future changes to the arrayOfObjectModelAVal property. The problem is that I'm using a webservice to retrieve JSON data to create an array of ObjectModelB ([[String : Any]]), and I build it like this:
guard let objectDictArray = responseObject as? [[String : Any]] else { return }
let objects = objectDictArray.compactMap({ try? ObjectModelB(any: $0) })
My objects get created inside the compactMap closure, and init doesn't trigger the didSet.
I also can't "override" the init provided by the init from the Decodable protocol (the one in the closure: try? ObjectModelB(any: $0)) because my object is a struct and this isn't inheritance, it's just an initializer provided by a protocol. Otherwise, I'd "override" the init in my object and then just do super.init followed by some sort of mutating function that updates my complicated property and I'd make my complicated property private(set).
The only other option I can think of is creating that mutating function I just mentioned, and calling it in both the didSet when arrayOfObjectModelAVal changes, and then update my object initialization call with something like this:
guard let objectDictArray = responseObject as? [[String : Any]] else { return }
let objects = objectDictArray
.compactMap({ try? ObjectModelB(any: $0) })
.forEach({ $0.updateProperties() })
But now updateProperties could be called at any time by anyone (which is bad because it's really taxing), and there's no guarantee that it even gets called when creating the array of objects because the dev could forget to do the forEach part. Hence why I want a way to automatically call the updateProperties function right after object initialization.
I figured out a way to accomplish this using a factory method. Like I said in the original question, the initializer I want to use is being provided by a protocol extension on Decodable (see Init an object conforming to Codable with a dictionary/array). I went ahead and added a createFrom static func inside of the Decodable extension like this:
extension Decodable {
init(any: Any) throws {
// https://stackoverflow.com/questions/46327302
}
static func createFrom(any: Any) throws -> Self {
return try Self.init(any: any)
}
}
Now if I define an init on ObjectModelB with the same function signature as the init provided in the Decodable extension, like so:
struct ObjectModelB: Codable {
var doubleVal: Double {
didSet {
computeComplicatedVar()
}
}
var arrayOfObjectModelAVal: [ObjectModelA] {
didSet {
computeComplicatedVar()
}
}
// Coding keys omitted for brevity
private(set) var complicatedVar: String = ""
mutating private func computeComplicatedVar() {
// complicated stuff here
}
init() {
doubleVal = 0.0
arrayOfObjectModelAVal = []
}
init(any: Any) throws {
self.init()
self = try ObjectModelB.createFrom(any: any)
computeComplicatedVar()
}
}
This seems to work. I like it because if I don't add the init that exactly matches the one provided in the Decodable extension, then my object can still use the one provided in the Decodable extension. But if I do provide my own, I just use the createFrom factory method to create an instance of my type using the init from Decodable, and then do whatever else I want after that. This way, I control which objects need special init treatment and which ones don't, but at the point of creating the object, nothing changes. You still use the same init(any:) function.

Making Realm & Unbox play nice

I am learning to parse JSON in Swift, coming from Android/Java, and I am using Unbox by John Sundell to help me with this, which reminds me of GSON.
Reference: Unbox pod
I use Realm as a database to store data locally.
Reference: Realm.io
It would be great to find a workflow to parse a class with JSON and save it to Realm. I don't want to have a struct that implements Unboxable AND a class that implements Object (Realm), because then I have to reflect the two. That isn't too much work for my current project, but it is kinda ugly...
Did any of you try a similar workflow?
I don't think you need two separate types. My suggestion is to create your objects as Swift classes that inherit from Realm's Object class, and then also conform them to the Unboxable protocol that Unbox offers. (Although the examples on Unbox's page use struct models, there's nothing in the code or documentation that indicates that classes wouldn't work.)
Realm model objects work just like any other classes: in addition to defining whatever properties on the objects you'd like stored in the database, you can also define methods and initializers, and even specify properties that you want Realm to ignore. This allows you to create an object that both serves as a Realm model and also a JSON model compatible with Unbox.
A more concise approach that doesn't require to override required initialisers (based on a tweet by Marin Todorov):
class Car: Object, Unboxable {
dynamic var vendor: String = ""
dynamic var modelName: String = ""
dynamic var electric: Bool = false
required convenience init(unboxer: Unboxer) throws {
self.init()
self.vendor = try unboxer.unbox(key: "vendor")
self.modelName = try unboxer.unbox(key: "modelName")
self.electric = try unboxer.unbox(key: "electric")
}
}
Here is an example that works perfectly for me:
class ProviderRealm: Object, Unboxable {
dynamic var identifier: String = "demo"
dynamic var name: String?
dynamic var logo: String?
/// Initializer used for unboxing of JSON string
required init(unboxer: Unboxer) throws {
self.identifier = (try? unboxer.unbox(key: "identifier")) ?? "demo"
self.name = try? unboxer.unbox(key: "name")
self.logo = try? unboxer.unbox(key: "logo")
super.init()
}
required init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}
required init() {
super.init()
}
required init(value: Any, schema: RLMSchema) {
super.init(value: value, schema: schema)
}
override static func primaryKey() -> String? {
return "identifier"
}
}

Dynamic attributes (getters/setters) in Swift on data models

I'm trying to use Swift (which I'm very new at) to create a clean interface for my data models. These models are backed by Firebase (though this is ultimately irrelevant to the actual question). What I want to avoid is writing tons of setter/accessor boiler plate code over and over.
For example:
class MyData {
let connection: Firebase!
private var _name: String? // internal storage, like an iVar in ObjC
init(connection: Firebase!) {
self.connection = connection
self.connection.observeEventType(.Value, withBlock: { snapshot in
_name = snapshot["name"]
}
}
var name: {
get {
return _name
}
set(name) {
// When the client sets the name, write it to Firebase
_name = name
self.connection.childByAppendingPath("name").setValue(name)
}
}
}
I'm sure I'm making a lot of mistakes in there. The idea is that the data is first loaded from the server when the instance is instantiated. Subsequently, we could call my_data_instance.name to get that name, or my_data_instance.name = "foo" and the name would be automatically written to the server.
This requires ~10 lines of code for a single attribute (of which there will be many). Nuts! There must be a better way!
EDIT: to be clear, I want to obviate the need to write as MUCH boiler plate code as possible. Consider a library like Mantle, where merely defining a #property is sufficient to do everything you want. In my opinion, anything more than one single line of code to say I have an attribute called "name", handle it via Firebase is overly verbose.
You can use Key-Value Observing to monitor changes in your properties. More info in Adopting Cocoa Design Patterns in Swift.
import Foundation
private var KVOContext = 0
// Your class must inherit from NSObject
class MyData : NSObject {
private let propertiesToObserve = ["name", "location"]
dynamic var name: String
dynamic var location: String
init(name: String, location: String) {
self.name = name
self.location = location
super.init()
// Add the properties that you want to observe
for property in self.propertiesToObserve {
self.addObserver(self, forKeyPath: property, options: [.New, .Old], context: &KVOContext)
}
}
// This method is called whenever an observed property is changed
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if let property = keyPath,
newValue = change![NSKeyValueChangeNewKey],
oldValue = change![NSKeyValueChangeOldKey] {
print("\(property) changed from \(oldValue) to \(newValue)")
// If oldValue != newValue, write back to Firebase
}
}
// Remove self as observer of self
deinit {
for property in self.propertiesToObserve {
self.removeObserver(self, forKeyPath: property)
}
}
}
let data = MyData(name: "John", location: "Chicago")
data.name = "David" // print: name changed from John to David
data.location = "New York" // print: location changed from Chicago to New York
Swift provides that functionality called property observer
var name: String {
didSet {
self.connection.childByAppendingPath("name").setValue(name)
}
}
There is a second observer willSet which is called before the value is changed.
Note (from the documentation):
When you assign a default value to a stored property, or set its
initial value within an initializer, the value of that property is set
directly, without calling any property observers.

How can I easily duplicate/copy an existing realm object

I have a Realm Object which has several relationships, anyone has a good code snippet that generalizes a copy method, to create a duplicate in the database.
In my case i just wanted to create an object and not persist it. so segiddins's solution didn't work for me.
Swift 3
To create a clone of user object in swift just use
let newUser = User(value: oldUser);
The new user object is not persisted.
You can use the following to create a shallow copy of your object, as long as it does not have a primary key:
realm.create(ObjectType.self, withValue: existingObject)
As of now, Dec 2020, there is no proper solution for this issue. We have many workarounds though.
Here is the one I have been using, and one with less limitations in my opinion.
Make your Realm Model Object classes conform to codable
class Dog: Object, Codable{
#objc dynamic var breed:String = "JustAnyDog"
}
Create this helper class
class RealmHelper {
//Used to expose generic
static func DetachedCopy<T:Codable>(of object:T) -> T?{
do{
let json = try JSONEncoder().encode(object)
return try JSONDecoder().decode(T.self, from: json)
}
catch let error{
print(error)
return nil
}
}
}
Call this method whenever you need detached / true deep copy of your Realm Object, like this:
//Suppose your Realm managed object: let dog:Dog = RealmDBService.shared.getFirstDog()
guard let detachedDog = RealmHelper.DetachedCopy(of: dog) else{
print("Could not detach Dog")
return
}
//Change/mutate object properties as you want
detachedDog.breed = "rottweiler"
As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our realm object. Just make sure all your Realm Model Classes conform to Codable.
Though its NOT an ideal solution, but its one of the most effective workaround.
I had a similar issue and found a simple workaround to get a copy of a realm object. Basically you just need to make the object conform to the NSCopying protocol, something like:
import RealmSwift
import Realm
import ObjectMapper
class Original: Object, NSCopying{
dynamic var originalId = 0
dynamic var firstName = ""
dynamic var lastName = ""
override static func primaryKey() -> String? {
return "originalId"
}
init(originalId: Int, firstName: String, lastName: String){
super.init()
self.originalId = originalId
self.firstName = firstName
self.lastName = lastName
}
func copy(with zone: NSZone? = nil) -> Any {
let copy = Original(originalId: originalId, firstName: firstName, lastName: lastName)
return copy
}
}
then you just call the "copy()" method on the object:
class ViewController: UIViewController {
var original = Original()
override func viewDidLoad() {
super.viewDidLoad()
var myCopy = original.copy()
}
}
The nice thing about having a copy is that I can modify it without having to be in a realm write transaction. Useful when users are editing some data but didn't hit save yet or simply changed their mind.
Since this problem is still alive I post my solution which works but still needs to be improved.
I've created an extension of Object class that has this method duplicate that takes an object objOut and fills the flat properties by looking at self. When a non-flat property is found (aka a nested object) that one is skipped.
// Duplicate object with its flat properties
func duplicate(objOut: Object) -> Object {
// Mirror object type
let objectType: Mirror = Mirror(reflecting: self);
// Iterate on object properties
for child in objectType.children {
// Get label
let label = child.label!
// Handler for flat properties, skip complex objects
switch String(describing: type(of: child.value)) {
case "Double", "Int", "Int64", "String":
objOut.setValue(self.value(forKey: label)!, forKey: label)
break
default:
break
}
}
return objOut
}
Inside the Manager class for my Realms I have the method copyFromRealm() that I use to create my copies of objects.
To give you a practical example this is the structure of my Appointment class:
Appointment object
- flat properties
- one UpdateInfo object
- flat properties
- one AddressLocation object
- flat properties
- one Address object
- flat properties
- one Coordinates object
- flat properies
- a list of ExtraInfo
- each ExtraInfo object
- flat properties
This is how I've implemented the copyFromRealm() method:
// Creates copy out of realm
func copyFromRealm() -> Appointment {
// Duplicate base object properties
let cpAppointment = self.duplicate(objOut: Appointment()) as! Appointment
// Duplicate UIU object
cpAppointment.uiu = self.uiu?.duplicate(objOut: UpdateInfo()) as? UpdateInfo
// Duplicate AddressLocation object
let cpAddress = self.addressLocation?.address?.duplicate(objOut: Address()) as? Address
let cpCoordinates = self.addressLocation?.coordinates?.duplicate(objOut: Coordinates()) as? Coordinates
cpAppointment.addressLocation = self.addressLocation?.duplicate(objOut: AddressLocation()) as? AddressLocation
cpAppointment.addressLocation?.address = cpAddress
cpAppointment.addressLocation?.coordinates = cpCoordinates
// Duplicate each ExtraInfo
for other in self.others {
cpAppointment.others.append(other.duplicate(objOut: ExtraInfo()) as! ExtraInfo)
}
return cpAppointment
}
I wasn't able to find out a good and reasonable way to work with nested objects inside my duplicate() method. I thought of recursion but code complexity raised too much.
This is not optimal but works, if I'll find a way to manage also nested object I'll update this answer.
Swift 5+
Creates a Realm managed copy of an existing Realm managed object with ID
extension RLMObject {
func createManagedCopy(withID newID: String) -> RLMObject? {
let realmClass = type(of: self)
guard let realm = self.realm, let primaryKey = realmClass.primaryKey() else {
return nil
}
let shallowCopy = realmClass.init(value: self)
shallowCopy.setValue(newID, forKey: primaryKey)
do {
realm.beginWriteTransaction()
realm.add(shallowCopy)
try realm.commitWriteTransaction()
} catch {
return nil
}
return shallowCopy
}
}