4
\$\begingroup\$

The below code works because of the do operator on line 24. Is there a more functional way to get the test to pass, or would you consider this an appropriate use of do?

import RxSwift


struct Token: Equatable {
    let rawValue: String
    static func ==(lhs: Token, rhs: Token) -> Bool {
        return lhs.rawValue == rhs.rawValue
    }
}

protocol Login {
    func getCredentials() -> Observable<(email: String, password: String)>
    func presentAuthFailure(_ error: Observable<Error>)
}

protocol Network {
    func getAuthToken(credentials: (email: String, password: String)) -> Observable<Token>
}

// MARK: - function Under Test
func checkCredentials(login: Login, network: Network) -> Observable<Token> {
    let credentials = login.getCredentials()
    let token = credentials.flatMap { network.getAuthToken(credentials: $0) }
        .do(onError: { login.presentAuthFailure(Observable.just($0)) }) // can this be done in some other way?
    return token.retry()
}

// MARK: - Test harness
class MockLogin: Login {
    func getCredentials() -> Observable<(email: String, password: String)> {
        assert(_credentials == nil)
        _credentials = PublishSubject()
        return _credentials!
    }

    func presentAuthFailure(_ error: Observable<Error>) {
        _ = error.subscribe(onNext: { [unowned self] error in
            assert(self._error == nil)
            self._error = error
        })
    }

    var _credentials: PublishSubject<(email: String, password: String)>?
    var _error: Error?
}

class MockNetwork: Network {
    func getAuthToken(credentials: (email: String, password: String)) -> Observable<Token> {
        assert(_token == nil)
        _token = PublishSubject()
        return _token!
    }

    var _token: PublishSubject<Token>?
}

func testTwoBadAttempts(login: MockLogin, network: MockNetwork) {
    checkCredentials(login: login, network: network).subscribe()

    assert(login._credentials != nil)

    let credentials = (email: "foo", password: "bar")
    login._credentials?.onNext(credentials)

    assert(network._token != nil)

    let error = NSError(domain: "testing", code: -1, userInfo: nil)
    network._token?.onError(error)

    assert(login._error != nil)

    network._token = nil
    login._error = nil

    login._credentials?.onNext(credentials)

    network._token?.onError(error)

    assert(login._error != nil)
}

let login = MockLogin()
let network = MockNetwork()
testTwoBadAttempts(login: login, network: network)
\$\endgroup\$
0
1
\$\begingroup\$

I found the solution to removing the do. I needed to move the error presentation into the flatMap and use materialize so that a network error wouldn't break the observable chain.

// MARK: - function Under Test
func checkCredentials(login: Login, network: Network) -> Observable<Token> {
    let credentials = login.getCredentials()
    let token = credentials.flatMap { (credential) -> Observable<Token> in
        let token = network.getAuthToken(credentials: credential)
            .materialize()
        let error = token.map { $0.error }.filter { $0 != nil }.map { $0! }
        login.presentAuthFailure(error)
        return token.map { $0.element }.filter { $0 != nil }.map { $0! }
    }
    return token
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.