Why can't I remove the NSUserDefaults key in swift? - swift

When I run the following test case, the testRemovePref method fails as it doesn't actually remove the value. I'm using Xcode 7.2. Why is this happening?
EDIT: This is only occurring when the tests belong to a Framework Library. The tests below seem to work fine when run in an Application. Odd.
import XCTest
class NSUserDefaultsTests: XCTestCase {
func testSetPref() {
let prefs = NSUserDefaults.standardUserDefaults()
prefs.setValue("value1", forKeyPath: "testKey")
let val : String? = prefs.stringForKey("testKey")
print("value=\(val)") // prints "value1"
XCTAssertEqual("value1", val)
}
func testRemovePref() {
let prefs = NSUserDefaults.standardUserDefaults()
prefs.removeObjectForKey("testKey")
let val : String? = prefs.stringForKey("testKey")
print("value=\(val)") // prints "value1"
XCTAssertNil(val)
}
}

This is only occurring when the tests belong to a Framework Library.
The tests below seem to work fine when run in an Application.
An application automatically synchronizes defaults when you send it to the background. This doesn't happen with tests. Try adding:
prefs.synchronize()

Related

Swift Combine PassThroughSubject not firing sink closure in XCTestCase

I was writing a unit test for some code I was working on in an iOS App and ran into very strange behavior.
The PassthroughSubject sink closure in my code wasn't running and thus the test was failing. When i uncomment the print line in the below code it passes. It also passes with no problems in a playground. I tried the whole quite Xcode delete derived data trick and that didn't work. I tried to move the class and unit test to a completely different project and it was still failing. To me this looks like an Apple bug but wanted to ask here to see if anyone has any insights. see the below code. Note i've simplified the code from my original project a bit to make is easier to read. This makes me feel like RxSwift is more polished than Combine at this point. Never saw anything like this happen with RxSwift.
class BadPassThroughSendClass {
let publisher = PassthroughSubject<Any, Never>()
var anyCancellable: AnyCancellable?
var sinkClosureBlock: (()->())?
init() {
createSubscription()
}
func createSubscription() {
let subscription = publisher
.subscribe(on: DispatchQueue.global())
.receive(on: DispatchQueue.global())
.sink { [unowned self] event in
sinkClosureBlock?()
}
anyCancellable = AnyCancellable(subscription)
}
}
class UnitTests: XCTestCase {
func testBadPassThroughClass() {
let badClass = BadPassThroughSendClass()
let sinkExpectation = expectation(description: "Sink closure should fire")
badClass.sinkClosureBlock = {
sinkExpectation.fulfill()
}
// print("pass the test with this line")
badClass.publisher.send("my test message")
waitForExpectations(timeout: 0.5)
}
}

Why can't my app find Firebase Functions?

I can't figure out why I keep getting the Swift warning: Use of unresolved identifier 'Functions on this line of my code: let functions = Functions.functions()
My imports for the viewController includes import Firebase and it works fine when I declare let db = Firestore.firestore() right above the line let functions = Functions.functions()
My podfile includes pod 'Firebase/Functions' and I've installed the pod.
I'm calling functions later using the following code and when I type "functions" it recommends adding .httpsCallable which leads me to believe that it actually does recognize the object "functions":
func getData(){
functions.httpsCallable("helloWorld").call(userData) { (result, error) in
if let error = error {
print(error)
}
if let data = result?.data {
print(data)
}
}
}
Figured it out. Importing Firebase is not enough, one must also import FirebaseFunctions (despite what Swift thinks, see screenshot below).

UserDefaults not working on Linux

UserDefaults does not appear to function in an XCTest when running on Linux. I'm running the below test but userDefaults.string(forKey: "key") returns nil on Linux when on macOS it returns the correct value and passes.
I'm using Docker to execute the tests on Linux. My Dockerfile is as follows:
FROM swiftdocker/swift
WORKDIR /package
COPY . ./
RUN swift package resolve
RUN swift package clean
RUN swift test --parallel
The test file:
import XCTest
class UserDefaultsTests: XCTestCase {
func testUserDefaults() {
let userDefaults = UserDefaults.standard
userDefaults.set("value", forKey: "key")
_ = userDefaults.synchronize()
XCTAssertEqual(userDefaults.string(forKey: "key"), "value") //nil is not equal to "value"
}
static var allTests = [
("testUserDefaults", testUserDefaults)
]
}
UserDefaults appears to be implemented in Swift Core Libs here, which is available on Linux, so I'm not sure why it wouldn't work.

Swift MVVM testing strategy and code coverage questions

I've run into an issue when generating code coverage with Xcode for view models in an MVVM environment.
Our basic setup is the view controller makes requests to the view model, which in turn calls methods on a data manager that talks to web services.
I came up with what I thought was a reasonably elegant way to test the view models by creating a fake data manager that subclasses the actual data manager and overrides the function called by the VM.
The problem is that for this to work, the VM must be part of the app target and the test target. An apparent side effect of this is that code coverage is not generated for items belonging to two or more targets, even though the unit tests pass. Code coverage is enabled in the project.
Here is a excerpted view model:
import Foundation
class BoosViewModel: BaseViewModel {
convenience override init() {
self.init(dataManager: BoosDataManager(), andModel: nil)
}
func getUnlinkedBoos(_ cardType: CardType) {
(dataManager as! BoosDataManager).getUnlinkedBoos(cardType) { result, error in
...stuff happens here...
}
}
}
... and the data manager
class BoosDataManager: DataManager {
static let SharedInstance: BoosDataManager = {
var manager = BoosDataManager()
return manager
}()
func getUnlinkedBoos(_ cardType: CardType = .loyalty, completion: #escaping ((_ result: BoosModel?, _ error: NSError?) -> Void)) {
...stuff happens here...
}
}
...and the test
class BoosViewModelTests: XCTestCase {
func testGetUnlinkedBoosHappyPath() {
class FauxDataManager: BoosDataManager {
override func getUnlinkedBoos(_ cardType: CardType = .loyalty, completion: #escaping ((_ result: BoosModel?, _ error: NSError?) -> Void)) {
...stuff happens here...
}
}
let viewModel = BoosViewModel()
let dataManager = FauxDataManager()
viewModel.dataManager = dataManager
viewModel.getUnlinkedBoos(.loyalty)
XCTAssertTrue(testObserver.updated)
XCTAssertEqual(testObserver.newViewModel.getBoos().count, 1)
}
}
As I noted earlier the unit tests in this scenario complete successfully, but unit coverage does not get generated.
I have older tests where I actually created an external fake data manager class that was used by the test, the class under test is not part of the test target, and coverage works fine.
The drawback to that is that I have to create multiple data managers to handle specific cases for its returns. If I can't encapsulate the classes, I would need to create a bunch of swift data managers, one for each scenario.
That's why I came up with the internal class.
Now, the problem comes in if I remove the view model under test from the testing target. After doing this, I add #testable import BoosApp to the unit test so that the view model under test can be resolved. When I do this, I get the following error:
Could not cast value of type 'BoosTests.BoosViewModelTests.(testGetUnlinkedBoosHappyPath () -> ()).(FauxDataManager #1)' (0x11f673d18) to 'Boos.BoosDataManager' (0x10444b128).
Aug 30 20:43:01 Pay[19025] : Could not cast value of type 'BoosTests.BoosViewModelTests.(testGetUnlinkedBoosHappyPath () -> ()).(FauxDataManager #1)' (0x11f673d18) to 'Boos.BoosDataManager' (0x10444b128).
I'm not sure what I'm missing. Is there a way to make this scenario work, or am I stuck creating multiple data managers outside of the test code?
Ultimately, I figured out the main issue was that the view model and data manager had somehow gotten added to the test target. After removing them from the test target I was able to make a couple of minor changes and everything is running fine. FYI.

Socket IO iOS sdk confuses xCode

installed socket.io sdk using cocoapods:
pod 'Socket.IO-Client-Swift'
Once I import it
import SocketIO
I get this weird warning and whenever I run the app it crashes:
item is referred as the following:
func createChatBox(item: AnyObject) -> UIView {
message.text = (item["message"] as! String)
}
And called as the following:
createChatBox(item: messages[0] as AnyObject)
Where
var messages:[Dictionary<String, AnyObject>] = [Dictionary<String, AnyObject>]()
With declaration
messages = [["type": 1 as AnyObject, "message" : "Hello" as AnyObject]]
Everything works fine without import SocketIO, I don't really know what is the problem with SocketIO and my variables.
Also, accessing the data directly without my functions works fine, as the following:
print(messages[0]["message"] as! String)
Thanks in advance.