I followed the guide on Realm's website, but my test are still producing varying results, depending on the random order they are done.
Either they succeed when run individually, or fail because the data was already set. For example:
XCTAssert(observer.events[0].value.element == "", "Initail hub name is expected to be equal to be an empty string but was \(String(describing: observer.events[0].value.element))")
XCTAssert(observer.events[1].value.element == testHubName, "Initail hub name is expected to be equal to be the name we set but was \(String(describing: observer.events[1].value.element))")
In this example, event[0] is already set to testHubName.
Or it gets a #throw RLMException(#"Object has been deleted or invalidated.");
So I guess there is some kind race going around, but this is in my setup method:
func setupRealm() {
testRealm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "DORSessionViewModelTestsRealm"))
try! self.testRealm.write {
self.testRealm.deleteAll()
}
}
So as far as I understand I should have a fresh Realm for every test, so where is the race going on?
Edit:
Adding a bigger part of the class
class DORSessionViewModelTests: XCTestCase {
var disposeBag: DisposeBag = DisposeBag()
var scheduler = TestScheduler(initialClock: 0)
var testRealm: Realm!
override func setUp() {
super.setUp()
setupRealm()
setupRx()
}
override func tearDown() {
super.tearDown()
}
func testHubNameLabelTextUpdate() {
let expectation = self.expectation(description: "expect hub name text to update")
let testHubName = "testHubName"
let viewModel = getSessionViewModel()
let observer = scheduler.createObserver(String.self)
scheduler.start()
let hubNameDriver = viewModel.hubNameLabelText()
hubNameDriver.asObservable().subscribe(observer).disposed(by: disposeBag)
hubNameDriver.asObservable().skip(1).subscribeNext { _ in expectation.fulfill()}.disposed(by: disposeBag)
try! self.testRealm.write {
viewModel.dependencies..hub.name = testHubName
}
waitForExpectations(timeout: 5, handler: { error in
XCTAssert(observer.events[0].value.element == "", "Initail hub name is expected to be equal to be an empty string but was \(String(describing: observer.events[0].value.element))")
XCTAssert(observer.events[1].value.element == testHubName, "Initail hub name is expected to be equal to be the name we set but was \(String(describing: observer.events[1].value.element))")
})
}
func setupRx() {
disposeBag = DisposeBag()
scheduler = TestScheduler(initialClock: 0)
}
Related
Say I get below code, and it works fine.
override func viewDidLoad() {
super.viewDidLoad()
// 1. put loadLevel() in background queue
DispatchQueue.global().async { [weak self] in
self?.loadLevel()
}
}
func loadLevel() {
var clueString = ""
var solutionString = ""
var letterBits = [String]()
// 2. some heavy code here
DispatchQueue.main.async { [weak self] in
// 3. push some UI code back to main thread
}
However, when I move the background queue to inside loadLevel() and cover the heavy code and UI code, I get an issue that UI is updated with empty values when launching the app. So what is the different of this two ways?
override func viewDidLoad() {
super.viewDidLoad()
// 1. call loadLevel
loadLevel()
}
func loadLevel() {
var clueString = ""
var solutionString = ""
var letterBits = [String]()
DispatchQueue.global().async { [weak self] in
// 2. some heavy code in background queue
DispatchQueue.main.async {
// 3. push UI code back to main thread
}
}
}
Update the 2nd code with the heavy code inside.
I found the issue, it is not related with GCD actually. This issue is in line Bundle.main.url(forResource: "level\(self?.level)", which produces a String interpolation warning. And result resource load get nil I guess.
As I used weak reference [weak self] as capture list here, I need to put self? before the global variable level in case to use it in closure. If I give it a default value like \(self?.level ?? 0), then this issue is fixed.
But is it that the property way to deal with this String interpolation here? Or some better approach should be involved here?
override func viewDidLoad() {
super.viewDidLoad()
// 1. call loadLevel
loadLevel()
}
func loadLevel() {
var clueString = ""
var solutionString = ""
var letterBits = [String]()
DispatchQueue.global().async { [weak self] in
if let levelFileURL = Bundle.main.url(forResource: "level\(self?.level)", withExtension: "txt") {
if let levelContents = try? String(contentsOf: levelFileURL) {
var lines = levelContents.components(separatedBy: "\n")
lines.shuffle()
self?.correctGuess = 0
print("AAA")
for (index, line) in lines.enumerated() {
let parts = line.components(separatedBy: ": ")
let answer = parts[0]
let clue = parts[1]
clueString += "\(index + 1). \(clue)\n"
let solutionWord = answer.replacingOccurrences(of: "|", with: "")
solutionString += "\(solutionWord.count) letters\n"
self?.solutions.append(solutionWord)
let bits = answer.components(separatedBy: "|")
letterBits += bits
print("ABC")
}
}
}
DispatchQueue.main.async {
// 3. push UI code back to main thread
}
}
}
You have a reference to:
let resource = Bundle.main.url(forResource: "level\(self?.level)" withExtension: ...)
The warning is
String interpolation produces a debug description for an optional value; did you mean to make this explicit?
The compiler is warning you that you are performing string interpolation of an optional value.
Let's consider a simpler example, to show what happens when you do string interpolation with optionals:
print("\(self?.level)")
If level was xxx, it would print
Optional("xxx")
And obviously if self or level were optional, it would just say:
nil
Clearly, neither of these are quite what you want. So, unwrap the optional. E.g.
guard let level = self?.level else { return }
let resource = Bundle.main.url(forResource: "level\(level)" withExtension: ...)
Let me start off by saying, I have no idea, but I have an idea for you to test. Move DispatchQueue.global().async to the first line of loadLevel().
func loadLevel() {
DispatchQueue.global().async { [weak self] in
var clueString = ""
var solutionString = ""
var letterBits = [String]()
// 2. some heavy code in background queue
DispatchQueue.main.async {
// 3. push UI code back to main thread
}
}
}
This isolates the change to just calling loadLevel(). If this works as expected, then keep moving the DispatchQueue.global().async call down until it does break.
func loadLevel() {
var clueString = ""
DispatchQueue.global().async { [weak self] in
var solutionString = ""
var letterBits = [String]()
// 2. some heavy code in background queue
DispatchQueue.main.async {
// 3. push UI code back to main thread
}
}
}
I'm learning Swift from a Udemy tutorial that shows how to make a chat app using a Firebase database. For my own learning and as a quick reference guide, I typed the code in a single .swift file to get a quick overview of the entire app and practice debugging. However, I have one more compiler error saying that the constant 'messagesDB' doesn't have a member 'setValue'. I'm assuming that messagesDB being an instance of class 'Database' would have access to the setValue() instance method. What do you think I'm missing in order to silence this error? Does it have something to do with the way the functions are declared?
Both class Auth and Database are arbitrary classes to mimic Firebase, so that the rest of the code could be displayed without a bunch of errors, thus giving me a single file to see how things work.
class Auth {
var currentUser: String = ""
func auth() -> Self { return self }
}
class Database {
func setValue() -> Self { return self }
func database() -> Self { return self }
func reference() -> Self { return self }
func child(_ someString: String) -> String {
print(someString)
}
}
class ChatViewController: UIViewController {
#IBOutlet var messageTextfield: UITextField!
#IBOutlet var sendButton: UIButton!
#IBAction func sendPressed(_ sender: AnyObject) {
messageTextfield.endEditing(true)
messageTextfield.isEnabled = false
sendButton.isEnabled = false
let messagesDB = Database().database().reference().child("Messages")
let messageDictionary = ["Sender": Auth().auth().currentUser, "MessageBody": messageTextfield.text!] as [String : Any]
messagesDB.setValue(messageDictionary) { //ERROR: Value of type 'String' has no member 'setValue'
(error, reference) in
if error != nil {
print(error!)
} else {
print("Message saved successfully!")
self.messageTextfield.isEnabled = true
self.sendButton.isEnabled = true
self.messageTextfield.text = ""
}
}
}
}
It's easy, your messageDB constant it's being set equals to the function "child" that returns a string, that's why it's telling you that "string" doesn't has no member setValue, so you should just set your messageDB to Database().database().reference()
the child method returns a String and not a Database instance, hence the error message.
create the database instance first and then use it where needed:
let messagesDB = Database()
.
.
messagesDB.setValue(...
I'm working on iOS App that uses the IP Stack API for geolocation. I'd like to optimise the IP Stack Api usage by asking for external (public) IP address first and then re-use lat response for that IP if it hasn't changed.
So what I'm after is that I ask every time the https://www.ipify.org about external IP, then ask https://ipstack.com with given IP address. If I ask the second time but IP doesn't changed then re-use last response (or actually cached dictionary with IP's as keys and responses as values).
I have a solution but I'm not happy with this cache property in my code. It is some state and some other part of code can mutate this. I was thinking about using some scan() operator in RxSwfit but I just can't figure out any new ideas.
class ViewController: UIViewController {
#IBOutlet var geoButton: UIButton!
let disposeBag = DisposeBag()
let API_KEY = "my_private_API_KEY"
let provider = PublicIPProvider()
var cachedResponse: [String: Any] = [:] // <-- THIS
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func geoButtonTapped(_ sender: UIButton) {
// my IP provider for ipify.org
// .flatMap to ignore all nil values,
// $0 - my structure to contains IP address as string
let fetchedIP = provider.currentPublicIP()
.timeout(3.0, scheduler: MainScheduler.instance)
.flatMapLatest { Observable.from(optional: $0.ip) }
.distinctUntilChanged()
// excuse me my barbaric URL creation, it's just for demonstration
let geoLocalization = fetchedIP
.flatMapLatest { ip -> Observable<Any> in
// check if cache contains response for given IP address
guard let lastResponse = self.cachedResponse[ip] else {
return URLSession.shared.rx.json(request: URLRequest(url: URL(string: "http://api.ipstack.com/\(ip)?access_key=\(API_KEY)")! ))
.do(onNext: { result in
// store cache as a "side effect"
print("My result 1: \(result)")
self.cachedResponse[ip] = result
})
}
return Observable.just(lastResponse)
}
geoLocalization
.subscribe(onNext: { result in
print("My result 2: \(result)")
})
.disposed(by: disposeBag)
}
}
Is it possible to achieve the same functionality but without var cachedResponse: [String: Any] = [:] property in my class?
OMG! I spent a bunch of time with the answer for this question (see below) and then realized that there is a much simpler solution. Just pass the correct caching parameter in your URLRequest and you can do away with the internal cache completely! I left the original answer because I also do a general review of your code.
class ViewController: UIViewController {
let disposeBag = DisposeBag()
let API_KEY = "my_private_API_KEY"
let provider = PublicIPProvider()
#IBAction func geoButtonTapped(_ sender: UIButton) {
// my IP provider for ipify.org
let fetchedIP: Maybe<String> = provider.currentPublicIP() // `currentPublicIP()` returns a Single
.timeout(3.0, scheduler: MainScheduler.instance)
.map { $0.ip ?? "" }
.filter { !$0.isEmpty }
// excuse me my barbaric URL creation, it's just for demonstration
let geoLocalization = fetchedIP
.flatMap { (ip) -> Maybe<Any> in
let url = URL(string: "http://api.ipstack.com/\(ip)?access_key=cce3a2a23ce22922afc229b154d08393")!
return URLSession.shared.rx.json(request: URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad))
.asMaybe()
}
geoLocalization
.observeOn(MainScheduler.instance)
.subscribe(onSuccess: { result in
print("My result 2: \(result)")
})
.disposed(by: disposeBag)
}
}
Original Answer
The short answer here is no. The best you can do is wrap the state in a class to limit its access. Something like this generic approach:
final class Cache<Key: Hashable, State> {
init(qos: DispatchQoS, source: #escaping (Key) -> Single<State>) {
scheduler = SerialDispatchQueueScheduler(qos: qos)
getState = source
}
func data(for key: Key) -> Single<State> {
lock.lock(); defer { lock.unlock() }
guard let state = cache[key] else {
let state = ReplaySubject<State>.create(bufferSize: 1)
getState(key)
.observeOn(scheduler)
.subscribe(onSuccess: { state.onNext($0) })
.disposed(by: bag)
cache[key] = state
return state.asSingle()
}
return state.asSingle()
}
private var cache: [Key: ReplaySubject<State>] = [:]
private let scheduler: SerialDispatchQueueScheduler
private let lock = NSRecursiveLock()
private let getState: (Key) -> Single<State>
private let bag = DisposeBag()
}
Using the above isolates your state and creates a nice reusable component for other situations where a cache is necessary.
I know it looks more complex than your current code, but gracefully handles the situation where there are multiple requests for the same key before any response is returned. It does this by pushing the same response object to all observers. (The scheduler and lock exist to protect data(for:) which could be called on any thread.)
I have some other suggested improvements for your code as well.
Instead of using flatMapLatest to unwrap an optional, just filter optionals out. But in this case, what's the difference between an empty String and a nil String? Better would be to use the nil coalescing operator and filter out empties.
Since you have the code in an IBAction, I assume that currentPublicIP() only emits one value and completes or errors. Make that clear by having it return a Single. If it does emit multiple values, then you are creating a new chain with every function call and all of them will be emitting values. It's unlikely that this is what you want.
URLSession's json(request:) function emits on a background thread. If you are going to be doing anything with UIKit, you will need to observe on the main thread.
Here is the resulting code with the adjustments mentioned above:
class ViewController: UIViewController {
private let disposeBag = DisposeBag()
private let provider = PublicIPProvider()
private let responses: Cache<String, Any> = Cache(qos: .userInitiated) { ip in
return URLSession.shared.rx.json(request: URLRequest(url: URL(string: "http://api.ipstack.com/\(ip)?access_key=cce3a2a23ce22922afc229b154d08393")!))
.asSingle()
}
#IBAction func geoButtonTapped(_ sender: UIButton) {
// my IP provider for ipify.org
let fetchedIP: Maybe<String> = provider.currentPublicIP() // `currentPublicIP()` returns a Single
.timeout(3.0, scheduler: MainScheduler.instance)
.map { $0.ip ?? "" }
.filter { !$0.isEmpty }
let geoLocalization: Maybe<Any> = fetchedIP
.flatMap { [weak responses] ip in
return responses?.data(for: ip).asMaybe() ?? Maybe.empty()
}
geoLocalization
.observeOn(MainScheduler.instance) // this is necessary if your subscribe messes with UIKit
.subscribe(onSuccess: { result in
print("My result 2: \(result)")
}, onError: { error in
// don't forget to handle errors.
})
.disposed(by: disposeBag)
}
}
I'm afraid that unless you have some way to cache your network responses (ideally with URLRequest's native caching mechanism), there will always be side-effects.
Here's a suggestion to try to keep them contained though:
Use Rx for your button tap as well, and get rid of the #IBAction. It's not great to have all that code in an #IBAction anyway (unless you did that for demonstration purposes).
That way, you can use a local-scope variable inside the setup function, which will only be captured by your flatMapLatest closure. It makes for some nice, clean code and helps you make sure that your cachedResponse dictionary is not tampered by other functions in your class.
class ViewController: UIViewController {
#IBOutlet var geoButton: UIButton!
let disposeBag = DisposeBag()
let API_KEY = "my_private_API_KEY"
let provider = PublicIPProvider()
override func viewDidLoad() {
super.viewDidLoad()
prepareGeoButton()
}
func prepareGeoButton() {
// ----> Use RxCocoa UIButton.rx.tap instead of #IBAction
let fetchedIP = geoButton.rx.tap
.flatMap { _ in self.provider.currentPublicIP() }
.timeout(3.0, scheduler: MainScheduler.instance)
.flatMapLatest { Observable.from(optional: $0.ip) }
.distinctUntilChanged()
// ----> Use local variable.
// Still has side-effects, but is much cleaner and safer.
var cachedResponse: [String: Any] = [:]
let geoLocalization = fetchedIP
.flatMapLatest { ip -> Observable<Any> in
// check if cache contains response for given IP address
guard let lastResponse = cachedResponse[ip] else {
return URLSession.shared.rx.json(request: URLRequest(url: URL(string: "http://api.ipstack.com/\(ip)?access_key=cce3a2a23ce22922afc229b154d08393")! ))
.do(onNext: { result in
print("My result 1: \(result)")
cachedResponse[ip] = result
})
}
return Observable.just(lastResponse)
}
geoLocalization
.subscribe(onNext: { result in
print("My result 2: \(result)")
})
.disposed(by: disposeBag)
}
}
I did not want to change the code too much from what you had and add more implementation details into the mix, but if you choose to go this way, please:
a) Use a Driver instead of an observable for your button tap. More on Drivers here.
b) Use [weak self] inside your closures. Don't retain self as this might lead to your ViewController being retained in memory multiple times when you move away from the current screen in the middle of a network request, or some other long-running action.
I'm trying to write a test to make sure that my view model's model property when set calls my fetchPlan method from the model and then sets my 'plan' property in my view model. It seems to be setting the property but the values are missing...
Here's my view model
final class PlanProgressViewModel: PlanProgressViewModelView {
// MARK: - Properties
fileprivate var plan: PlanData?
// MARK: - PlanProgressViewModelView
weak var viewDelegate: PlanProgressViewModelViewDelegate?
var model: PlanModel? {
didSet {
model?.fetchCurrentPlan(completionHandler: { (plan) in
guard let plan = plan else {return}
self.plan = plan
})
}
}
// Testing this fails...
var planName: String! {
guard let plan = plan else {return "No plan"}
return plan.name
}
var planProgressionString: String! {
return "\(Int(round(self.progress * 100)))%"
}
var progress: Double! {
guard let plan = plan, let workouts = plan.workouts, let completedWorkouts = plan.completedWorkouts else {return 0}
return Double(Int(completedWorkouts) / workouts.count)
}
}
Here's my test suite, i'm using a mock to return hardcoded data from the model.
var sut: PlanProgressViewModel!
var model: MockPlanModel!
var moc: NSManagedObjectContext!
override func setUp() {
super.setUp()
moc = setupInMemoryMOC()
let mockModel = MockPlanModel(moc: moc)
model = mockModel
let viewModel = PlanProgressViewModel()
viewModel.model = model
sut = viewModel
}
override func tearDown() {
moc = nil
model = nil
sut = nil
super.tearDown()
}
// This passes
func testModelFetchesCurrentPlanOnce() {
XCTAssertEqual(model.fetchPlanWasCalled, 1)
}
// This is failing
func testPlanName() {
XCTAssertEqual(sut.planName, "Test plan")
}
Here's my method for setting up a in memory persistent store...
public func setupInMemoryMOC() -> NSManagedObjectContext {
let mom = NSManagedObjectModel.mergedModel(from: [Bundle.main])
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom!)
do {
try psc.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
} catch {
fatalError()
}
let moc = NSManagedObjectContext.init(concurrencyType: .mainQueueConcurrencyType)
moc.persistentStoreCoordinator = psc
return moc }
Here's my mock for the model which returns hardcoded data...
public class MockPlanModel: MWPlanModel {
var fetchPlanWasCalled = 0
override public func fetchCurrentPlan(completionHandler: #escaping (_ plan: PlanData?) -> ()) {
fetchPlanWasCalled += 1
let moc = setupInMemoryMOC()
let plan = createTestPlan(moc: moc)
completionHandler(plan)
}}
Here's my helper method for creating the model object, (PlanData is a protocol that my 'Plan' NSManaged object inherits).
public func createTestPlan(moc: NSManagedObjectContext) -> PlanData {
let plan: Plan = Plan(context: moc)
plan.name = "Test plan"
plan.completedWorkouts = 5
plan.currentPlan = true
for _ in 0..<5 {
plan.mutableOrderedSetValue(forKeyPath: #keyPath(Plan.workouts)).add(createTestCompletedWorkout(moc: moc))
}
return plan }
The plan name should be "Test plan" as that is what i set the hardcoded value to be but it fails and returns an empty string instead...
Really stuck on this, i'm fairly new to testing so i appreciate any help with this. Thanks
#MartinR comment good point out :
The non-optional on the left side "Test plan" gets automatically promoted to an optional. because sut.planName is an optional. more check this Swift comparing Strings optionals vs non-optional
You need to unwrap sut.planName
if let planName = sut.planName{
XCTAssertEqual(planName, "Test plan")
}
Or try this way :
XCTAssertEqual((sut.planName ?? ""), "Test plan")
Note: If you face still same issue then update your Xcode
// Using Apple XCTest (Xcode 7.3.1), this produces the output:
// "XCTAssertEqual failed: ("Optional(1)") is not equal to ("Optional(2)") - "
XCTAssertEqual(1, 2)
More details : Here Bug reported
So I have this viewModel which has a validation observable that is simply the combination of 5 other signals into a boolean.
import RxSwift
class SchedulingFormViewModel: BaseViewModel {
let places = Variable<[String]>([])
var formIsValid: Observable<Bool>!
override init() {
super.init()
places.value = ["LUGAR 1", "LUGAR 2", "LUGAR 3"]
formIsValid = Observable.combineLatest(UserSession.currenctScheduling.dateSignal.asObservable(),
UserSession.currenctScheduling.carSignal.asObservable(),
UserSession.currenctScheduling.locationSignal.asObservable(),
UserSession.currenctScheduling.servicesSignal.asObservable())
{ (date: Date?, car: Car?, location: String?, services: [Service]?) in
print("DATE: \(date), CAR: \(car), LOCATION: \(location), SERVICES:\(services)")
guard let servicesArray = services else { return false }
let valid = date != nil && car != nil && location != nil && servicesArray.count > 0
return valid
}
}
}
And then I have my test class with a method that should just test whether the signal changes or not. I've tried a variety of approaches but none of them really gets the true value at the end. The observable only emits a true signal after the test has run.
My test class is setup like this
import XCTest
import RxSwift
import RxTest
#testable import Automobi
class SchedulingUnitTests: XCTestCase {
var viewModel: SchedulingFormViewModel!
var disposeBag: DisposeBag!
var scheduler: TestScheduler!
override func setUp() {
super.setUp()
viewModel = SchedulingFormViewModel()
UserSession.clearScheduling()
disposeBag = DisposeBag()
scheduler = TestScheduler(initialClock: 0)
}
}
I've tried the TestScheduler
func testFormValidation() {
UserSession.currenctScheduling.dateSignal.value = Date()
UserSession.currenctScheduling.carSignal.value = Car()
UserSession.currenctScheduling.servicesSignal.value = [Service(), Service()]
let location = scheduler.createHotObservable([
next(100, "TEST")
])
location.subscribe(onNext: { (text) in
print("LOCATION: \(text)")
UserSession.currenctScheduling.locationSignal.value = text
}).addDisposableTo(disposeBag)
let results = scheduler.createObserver(Bool.self)
scheduler.scheduleAt(0) {
self.viewModel.formIsValid.subscribe(results).addDisposableTo(self.disposeBag)
}
let expected = [
next(100, true)
]
scheduler.start()
XCTAssertEqual(results.events, expected)
}
Also
func testFormValidation() {
UserSession.currenctScheduling.dateSignal.value = Date()
UserSession.currenctScheduling.carSignal.value = Car()
UserSession.currenctScheduling.servicesSignal.value = [Service(), Service()]
let location = scheduler.createHotObservable([
next(100, "TEST"),
next(150, "TEST 2"),
next(200, "TEST 3")
])
location.subscribe(onNext: { (text) in
print("LOCATION: \(text)")
UserSession.currenctScheduling.locationSignal.value = text
}).addDisposableTo(disposeBag)
var results = [Bool]()
viewModel.formIsValid.subscribe(onNext: { (value) in
results.append(value)
}).addDisposableTo(disposeBag)
let expected = [true, true, true]
scheduler.start()
XCTAssertEqual(results, expected)
}
I've tried things like binding my formIsValid to a Variable and verifying its value at the end.
func testFormValidation() {
UserSession.currenctScheduling.dateSignal.value = Date()
UserSession.currenctScheduling.carSignal.value = Car()
UserSession.currenctScheduling.locationSignal.value = "TESTE"
UserSession.currenctScheduling.servicesSignal.value = [Service(), Service()]
sleep(5)
XCTAssertTrue(viewModel.formIsValid.value)
}
But I never get the expected result. I do get a true signal after all the tests fail and the code goes back to executing. Also when running the app the code executes as expecte I just need to catch it in the test. Any ideas?!
On your first try, you are probably getting a value on clock 0, so your expected value should be:
let expected = [
next(0, false)
next(100, true)
]