I have 2 functions where one basically retrieves string in User Defaults and another writes the string in User Defaults. I'm not able to understand that should I do the unit test or no? and I'm pretty new to the concept for Unit testing.
I scoured the internet for testing user defaults but I was advised to mock the test in the end.
My question is If there is a way to test User Defaults what is the best way to do it?
Constants and Structs
let defaults = UserDefaults.standard
let defaultinformation = "ABCDEFG"
struct Keys {
static let Information = "Information"
}
Function where saves Information
func SetDefaultInformation() {
defaults.set(defaultinformation, forKey: Keys.Information)
}
Function where retrieves Information
func checkForInformation() -> String {
let Information = defaults.value(forKey: Keys.Information) as? String ?? ""
return Information
}
Thanks in Advance
should I do the unit test or no
No. You know what UserDefaults does and you know how it works and that it works. It is ridiculous to test Apple's code. Testing defaults.set is just as silly; you know exactly what it does and you know that it will do it.
What you want to test is your code: not your retrieval from UserDefaults per se, but your response to that retrieval. Give yourself methods such that you can see what you do when information is a String and what you do when information is nil. As you've been told, a trivial mock can supply the back end, playing the part of UserDefaults and giving back different sorts of result. Just don't let your tests involve the real UserDefaults.
class ViewModel {
func saveUserName(name: String, userDefaults: UserDefaults = UserDefaults.standard) {
userDefaults.set(name, forKey: "username")
}
}
class MockUserDefault: UserDefaults {
var persistedUserName: String? = nil
var persistenceKey: String? = nil
override func set(_ value: Any?, forKey defaultName: String) {
persistedUserName = value as? String
persistenceKey = defaultName
}
}
func testSavingUserName() {
let viewModel = ViewModel()
let mockUserDefaults = MockUserDefault()
viewModel.saveUserName(name: "Oliver", userDefaults: mockUserDefaults)
XCTAssertEqual("Oliver", mockUserDefaults.persistedUserName)
XCTAssertEqual("username", mockUserDefaults.persistenceKey)
}
Related
we're using Locksmith to save user data for Keychain. In our end everything works as it should but for some reason we receive crashes with the error Locksmith.LocksmithError.interactionNotAllowed.
Follows the code where the crash happen:
func updateUserAccessToken(forAccount account: String, token: String) {
var userAccessToken = Locksmith.loadDataForUserAccount(userAccount: account) ?? [String: Any]()
userAccessToken[“token”] = token
try! Locksmith.updateData(data: userAccessToken, forUserAccount: account)
}
Why is the code above crashes for other users? 'til . now we can't replicate the said crash. Any help is very much appreciated. Thanks!
UPDATE:
So we finally able to replicate this crash, and it's because we're accessing the keychain while the device is locked. I found out you can change the Keychain's "accessibility option" but I'm not sure how to do it in Locksmith. Anybody?
I found out that if you're using a protocol base approach changing the accessibility option is much more easier. But unfortunately our app doesn't use it. So what I did was I created an extension as follow:
extension Locksmith {
fileprivate static func loadDataForUserAccount(userAccount: String,
inService service: String = LocksmithDefaultService,
accessibleOption: LocksmithAccessibleOption) -> [String: Any]? {
struct ReadRequest: GenericPasswordSecureStorable, ReadableSecureStorable {
let service: String
let account: String
var accessible: LocksmithAccessibleOption?
}
let request = ReadRequest(service: service, account: userAccount, accessible: accessibleOption)
return request.readFromSecureStore()?.data
}
fileprivate static func updateData(data: [String: Any],
forUserAccount userAccount: String,
inService service: String = LocksmithDefaultService,
accessibleOption: LocksmithAccessibleOption) throws {
struct UpdateRequest: GenericPasswordSecureStorable, CreateableSecureStorable {
let service: String
let account: String
let data: [String: Any]
var accessible: LocksmithAccessibleOption?
}
let request = UpdateRequest(service: service, account: userAccount, data: data, accessible: accessibleOption)
return try request.updateInSecureStore()
}
}
NOTE: changing the "accessibility option" may loss your access to the data previously saved with the default "accessibility option". If you need those data you may need to handle it separately.
So basically I have a function that connects to Firebase and gets data in the form of a string and returns a string(at least I think).
this question Is very simple and kind of a dumb question but how would I call this method in a different thread or core. Sorry don't really know the terms yet. I am trying.
also, I don't think it returns a String which I need so how would I accomplish that?
also this is not a duplicate because if you look at all these type of questions I have not found one that calls the actual method.
typealias someting = (String?) -> Void
func getOpposingUsername( _ index: Int, completionHandler: #escaping someting) {
var opposingUser: String = ""
self.datRef.child("Bets").child(self.tieBetToUser[index]).observe(.childAdded, with: { snapshot in
guard let dict = snapshot.value as? [String: AnyHashable] else {
return
}
opposingUser = dict["OpposingUsername"] as! String
if opposingUser.isEmpty {
completionHandler(nil)
} else {
completionHandler(opposingUser)
}
})
}
Changing threads would be something like this DispatchQueue.main.async {your code} here is the documentation on GCD Dispatch.
Here is an example of a function that returns a string,
func stringReturn() -> String {
let aString = "a string"
return aString
}
I am creating simple Json Parser that works like that: I have JsonData class that contains Anyobject as data. When I use jsonData["key"] it returns JsonData to i can chain jsonData["key"]["key2"] etc.
My question is how can I implement that class so i could cast it to lets say String:
jsonData["key"] as String without using some workarouds like
jsonData["key"].data as String
Code:
class JsonData:CustomStringConvertible{
let data:AnyObject
var description: String{
get{
return "\(data)"
}
}
init(_ data: Data) {
self.data = try! JSONSerialization.jsonObject(with: data, options: []) as! [[String:AnyObject]]
}
init(_ data: AnyObject) {
self.data = data
}
subscript(key:String) -> JsonData{
let newData = data as! [String:AnyObject]
let test = newData[key]!
return JsonData(test)
}
subscript(index:Int) ->JsonData{
let newData = data[index]!
return JsonData(newData)
}
}
In order to do this, you'd add another overload, but it won't work like you're thinking.
subscript(key: String) -> String {
let newData = data as! [String:AnyObject]
return newData[key] as! String
}
So then jsonData["key"] as String works, but jsonData["key"]["key2"] is ambiguous and you'd have to write it (jsonData["key"] as JsonData)["key2"] which probably isn't what you want.
The short answer to this is don't do this. If you need this much access to JSON, you're probably storing your data incorrectly. Parse it to structs as quickly as you can, and then work with structs. Convert the structs back to JSON when you want that. Extensive work with AnyObject is going to break your brain and the compiler over and over again. AnyObject is a necessary evil, not an every day tool. Soon you will encounter that terrible day that you have an AnyObject? and the compiler just breaks down in tears. Well, at least it isn't Any.
Putting that aside, the better solution is to use labeled-subscripts.
subscript(string key: String) -> String {
let newData = data as! [String:AnyObject]
return newData[key] as! String
}
Now you can access that as json[string: "key"] rather than json["key"] as String.
I currently driving crazy cause i can't find my mistake.
I try to pass some information over to my WatchKit App, but this does not work for me.
Both are in the same group
Then i start with the Watch with
NSUserDefaults(suiteName: "group.WatchDate")
NSUserDefaults.standardUserDefaults().setObject("1", forKey: "TEST")
NSUserDefaults.standardUserDefaults().synchronize()
after launch the app once on the watch i switch over to the "phone app" and let it println() the stored value
NSUserDefaults(suiteName: "group.WatchDate")
println("Saved Informations:")
println(NSUserDefaults.standardUserDefaults().objectForKey("TEST"))
The output always is "nil"
I tried the same passing information the other way, also with no success.
But if i simply println() the stored value on the same "device" it works.
Any idea what i m doing wrong?
zisoft has the right point, but to my belief, my code is the safer and cleaner (more readable) way to do it. I´m also taking into consideration, that naming your App Group without using reverse-DNS-style is not considered best practice.
Set data:
import Foundation
if let sharedDefaults = NSUserDefaults(suiteName: "group.com.mycompany.WatchDate") {
sharedDefaults.setObject("1", forKey: "TEST")
sharedDefaults.synchronize()
}
Read data:
import Foundation
if let sharedDefaults = NSUserDefaults(suiteName: "group.com.mycompany.WatchDate") {
if let sharedString = sharedDefaults.objectForKey("TEST") as? String {
println(sharedString)
}
}
NSUserDefaults(suiteName) already returns a NSUserDefaults object, so you must not call standardUserDefaults() again on it. Try it this way:
let sharedDefaults = NSUserDefaults(suiteName: "group.WatchDate")
sharedDefaults?.setObject("1", forKey: "TEST")
sharedDefaults?.synchronize()
on the other side:
let sharedDefaults = NSUserDefaults(suiteName: "group.WatchDate")
let s = sharedDefaults?.objectForKey("TEST") as! String
println(s)
The code in my Keychain class:
struct Keys {
static var token: String = "MyAppToken"
...
}
internal class func set(key: String, value: String) -> Bool {
if let data = value.dataUsingEncoding(NSUTF8StringEncoding) {
return set(key, value: data)
}
return false
}
internal class func get(key: String) -> NSString? {
if let data = getData(key) {
return NSString(data: data, encoding: NSUTF8StringEncoding)
}
return nil
}
...
In unit test doing something like this works without any issues:
func testKeychain() {
let setKey = Keychain.set(Keychain.Keys.token, value: "test")
let getKey = Keychain.get(Keychain.Keys.token)
XCTAssertTrue(setKey, "set keychain value with string")
XCTAssertNotNil(getKey, "retrieve keychain value with key: \(Keychain.Keys.token)")
XCTAssertTrue(getKey!.isEqualToString("test"), "retrieved keychain value matches raw string")
}
In the live code, when I save the value the same way as in the test, everything looks ok. But when trying to retrieve the token the parameter Keychain.Keys.token becomes Chinese characters (except for the last character!?! -> "慐杩湡潔敫n" instead of "MyAppToken"). This causes the get(Keychain.Keys.token) call to return nil. How can something like this happen?
Edit:
This only happens in the "release" build configuration. So, I guess it has something to do with the build settings.
Apparently this issue was caused by the Swift Compiler Optimization. Turning the Optimization Level to "none" has solved it.