swiftrx-swiftrxtest

Trigger an event and wait for multiple events RxTest


It's my first time using RxTest and I am struggling how to do the following approach:

protocol ViewModelType {
    func transform(input: ViewModel.Input) -> ViewModel.Output
}

struct ViewModel: ViewModelType {
    private let isLoading = PublishSubject<Bool>()

    struct Input {
        let trigger: PublishSubject<Void>
    }

    struct Output {
        let someAction: Observable<Void>
        let isLoading: Observable<Bool>
    }

    func transform(input: Input) -> Output {
        let someAction = input
            .trigger
            .do(onNext: { _ in
                self.isLoading.onNext(true)
                //do some async task
                self.isLoading.onNext(false)
            })
        return Output(someAction: someAction, isLoading: isLoading)
    }
}

I have created a Publish Subject inside the viewModel to notify the view when it should show the loader or not.

Everything works fine, excepts I don't know how to test it with the RxTest framework.

I was trying to use the scheduler and cold observables but couldn't manage to make it work.

What I would like to have:

  1. With the scheduler send the .next(10, ()) to the trigger.
  2. Somehow record the events of the isLoading and assert equal that goes first true and then false. Like that: [.next(10, true), .next(20, false)].

Maybe, the isLoading the way I did it, it's not testable. But seems it's going out through the Output I think maybe there is some way.

Thank you so much, if something is unclear, please feel free to edit or guide me to a better question. Much appreciated.


Solution

  • A couple of things:

    Your Input struct should contain Observables, not subjects. That way you can attach to them properly.

    You don't want to use the do operator. Instead think about the problem from the output first. When trigger emits you want isLoading to emit true and you want the async task to start. That means you should have two observable chains. There's lots of sample code showing how to do this.

    In the mean time, here's your test (along with the required modifications to your code:

    
    class RxSandboxTests: XCTestCase {
    
        func testOne() {
    
            let scheduler = TestScheduler(initialClock: 0)
            let trigger = scheduler.createHotObservable([.next(10, ())])
            let someActionResult = scheduler.createObserver(Bool.self)
            let isLoadingResult = scheduler.createObserver(Bool.self)
            let bag = DisposeBag()
            let sut = ViewModel()
            let input = ViewModel.Input(trigger: trigger.asObservable())
            let output = sut.transform(input: input)
    
            bag.insert(
                output.someAction.map { true }.bind(to: someActionResult),
                output.isLoading.bind(to: isLoadingResult)
            )
            scheduler.start()
    
            XCTAssertEqual(someActionResult.events, [.next(10, true)])
            XCTAssertEqual(isLoadingResult.events, [.next(10, true), .next(10, false)])
        }
    
    }
    
    protocol ViewModelType {
        func transform(input: ViewModel.Input) -> ViewModel.Output
    }
    
    struct ViewModel: ViewModelType {
        private let isLoading = PublishSubject<Bool>()
    
        struct Input {
            let trigger: Observable<Void>
        }
    
        struct Output {
            let someAction: Observable<Void>
            let isLoading: Observable<Bool>
        }
    
        func transform(input: Input) -> Output {
            let someAction = input
                .trigger
                .do(onNext: { _ in
                    self.isLoading.onNext(true)
                    //do some async task
                    self.isLoading.onNext(false)
                })
            return Output(someAction: someAction, isLoading: isLoading)
        }
    }