2

I'm attempting to write a unit test for Driver from RxCocoa library. Here's my simplified implementation code:

struct LoginViewModel {

    var username: Driver<String?>!
    var password: Driver<String?>!
    var loginTaps: Driver<Void>!

    func login() -> Driver<LoginResult> {
        let credentials = Driver.combineLatest(username, password) { ($0, $1) }
        let latestCredentials = loginTaps.withLatestFrom(credentials)

        return latestCredentials.flatMapLatest { (username, password) in
            .just(.success)
        }
    }
}

And here's the Quick/Nimble unit test I'm attempting to pass:

let disposeBag = DisposeBag()
var capturedLoginResult = LoginResult.failed

loginViewModel.username = Driver.just("some username")
loginViewModel.password = Driver.just("some password")
loginViewModel.loginTaps = Driver.just()

loginViewModel.login().drive(onNext: { loginResult in
    capturedLoginResult = loginResult
}).addDisposableTo(disposeBag)

expect(capturedLoginResult == .success)

Above expect says that capturedLoginResult is still .failed. It appears as though element from return latestCredentials.flatMapLatest { (username, password) in .just(.success) } is not getting received by the .drive(onNext: ) in the test.

If the implementation of login is just:

func login() -> Driver<LoginResult> {
    return .just(.success)
}

The test passes.

Any thoughts on what's happening here? Thanks!

Alex
  • 739
  • 1
  • 6
  • 18
  • How are `username`, `password` and `loginTaps` configured in the test file? – tomahh Mar 02 '17 at 00:08
  • @tomahh they are defined as `username = Driver.just("some username")`, `password = Driver.just("some password")`, and `loginTaps = Driver.just()`. Updated question with these details! – Alex Mar 03 '17 at 00:51

1 Answers1

2

I don't know exactly where in Rx's source, but my guess is that an operator you are using is switching scheduler. Because of this, the subscription made with drive(onNext:) is not trigger immediately.

RxSwift provides a good API for testing our observables, through the RxTest package. You could rewrite your tests to take advantage of it.

let scheduler = TestScheduler(initialClock: 0)
let username = scheduler.createHotObservable([next(220, "username"), completed(20)])
let password = scheduler.createHotObservable([next(230, "p4ssw0rd"), completed(20)])
let loginTaps = scheduler.createHotObservable([next(240), completed(20)]) 

let recordObserver = scheduler.start(300) { () -> Observable<LoginResult> in
  let loginViewModel = LoginViewModel()

  loginViewModel.username = username.asDriver(onErrorJustReturn: "")
  loginViewModel.password = username.asDriver(onErrorJustReturn: "")
  loginViewModel.loginTaps = loginTaps.asDriver(onErrorJustReturn: ())

  return loginViewModel.login().asObservable()
}

let expectedEvents: [Recorded<Event<LoginResult>>] = [
  next(240, Login.success)
]

expect(recordObserver.events) == (expectedEvents)
tomahh
  • 13,441
  • 3
  • 49
  • 70