ReactiveX / RxSwift

Reactive Programming in Swift

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Enabling delays in error events

florianldt opened this issue · comments

Short description of the issue:

When mocking services (networking or else), I make use of the delay(_ dueTime: RxTimeInterval, scheduler: SchedulerType) -> Observable<Element> method. As mentioned in the comment, Error events from the source observable sequence are not delayed..

However, when mocking, I am also looking to mock error flows. I am using the following extension to enable delays in error events:

extension ObservableType {
    static func catchDelayedError<T>(_ error: Error, delay: DispatchTimeInterval, queue: DispatchQueue) -> Observable<T> {
        return Observable.create { observer in
            queue.asyncAfter(deadline: .now() + .seconds(2), execute: {
                observer.onError(error)
            })
            return Disposables.create()
        }
    }
}

This can be used like:

final class UserMockService {
    struct Options {
        let fetchUserShouldFail: Bool
    }

    private let options: Options

    init(options: Options) {
        self.options = options
    }
}

extension UserMockService: UserServiceType {
    func fetchUser() -> Observable<User> {
        if options.fetchUserShouldFail {
            return Observable<RefreshCodeResponse>
                .catchDelayedError(
                    GeneralHTTPError.authenticationFailure(),
                    delay: .seconds(2),
                    queue: DispatchQueue.global(qos: .background)
                )
        }

        let user = User(
            name: "Florian"
        )

        return Observable
            .just(user)
            .delay(.seconds(2), scheduler: ConcurrentDispatchQueueScheduler(qos: .background))
    }
}

Is there a built-in solution to achieve this? If not, is there another approach for it?

Have a great day.

IMO, setting up background schedulers and intentionally delaying unit tests is a mistake. Use a TestScheduler to make all your test synchronous for maximum speed. Instead of passing a UserServiceType to your system under test, pass in an Observable<User> directly. You can easily mock it by passing a TestableObservable instead of the normal one in the test environment.

If you want to have it "delay" for two seconds and then emit an error, just use scheduler.createColdObservable([.error(2, GeneralHTTPError.authenticationFailure)]) as TestableObservable<User>

For unit testing you are correct. What I wanted to say is that I use the mock service for development when the live API is not ready for instance. But still I'll look into TestScheduler and if it make sense here.

Ah, okay. For integration testing, I still recommend just passing the Observable directly instead of the UserServiceType. You can send a delayed error with this:

let obs = Observable.just(())
	.delay(.seconds(2), scheduler: MainScheduler.instance)
	.flatMap { Observable<User>.error(GeneralHTTPError.authenticationFailure) }

Yes you are correct. This is the simplest way to achieve what I was looking for.
Thanks a bunch.