I have a property on my view model:
let isValid: Driver<Bool>
let credentials: Driver<(String, String)>
......
credentials = .combineLatest(bindings.username, bindings.password, resultSelector: { (username, password) -> (String, String) in (username, password) })
isValid = credentials.map { username, password in username.count > 0 && password.count > 7 }
I'd like to assert that the correct state is set on isValid
when valid inputs are set.
My test is passing below, however this doesn't feel like the correct way to test this scenario.
Ideally I'd like to start with my strings as ""
and then pass in values as if they had been typed so I can assert the default state is set and then changes.
I also find these lines:
.do(onNext: { state in
if state {
exp.fulfill()
}
})
a little "hacky".
func test_is_valid_state_changes_when_inputs_correct_length() {
let username: Driver<String> = .of("some_user_name")
let password: Driver<String> = .of("some_user_password")
let bindings = LoginViewModel.Bindings(username: username, password: password, loginTap: .empty(), doneTap: .empty())
let sut = LoginViewModel(dependency: "", bindings: bindings)
let scheduler = TestScheduler(initialClock: 0)
let observer = scheduler.createObserver(Bool.self)
let exp = expectation(description: "isValid Event")
sut.isValid
.asObservable()
.do(onNext: { state in
if state {
exp.fulfill()
}
})
.subscribe(observer)
.disposed(by: disposeBag)
scheduler.start()
waitForExpectations(timeout: 0.5) { error in
XCTAssertNil(error)
XCTAssertEqual(observer.events.count, 1)
XCTAssertTrue(observer.events[0].value.element!) // swiftlint:disable:this force_unwrapping
}
}
You need to use TestObservables in order to do the testing you want and you don't need an expectation object because this test will complete without any threading issues.
func test_is_valid_state_changes_when_inputs_correct_length() {
let scheduler = TestScheduler(initialClock: 0)
let username = scheduler.createHotObservable([.next(0, ""), .next(10, "h")])
let password = scheduler.createHotObservable([.next(0, ""), .next(30, "p"), .next(40, "passwor"), .next(50, "password")])
let bindings = LoginViewModel.Bindings(
username: username.asDriver(onErrorRecover: { _ in XCTFail(); return .empty() }),
password: password.asDriver(onErrorRecover: { _ in XCTFail(); return .empty() }),
loginTap: .empty(),
doneTap: .empty()
)
let disposeBag = DisposeBag()
let sut = LoginViewModel(dependency: "", bindings: bindings)
let observer = scheduler.createObserver(Bool.self)
sut.isValid
.drive(observer)
.disposed(by: disposeBag)
scheduler.start()
XCTAssertEqual(observer.events, [
.next(0, false),
.next(10, false),
.next(30, false),
.next(40, false),
.next(50, true)
])
}