Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxSwift : how to Unit Test Searching in Searchbar and displaying results in tableview

So ive been using Rxswift for a while and its been working well. Ive managed to get all my code under test but Im struggling to figure out how to test searching with searchbar.rx.bindTo .

There are many tutorials of how to use RxSwift for searching and returning results on a tableview but in none of those tutorials do they show you how to unit test it. https://www.thedroidsonroids.com/blog/ios/rxswift-by-examples-1-the-basics/

The above linked shows what im trying to achieve with the searchbar and populating the TableView.

Ive tried testing it with RxBlocking but my tests all seem to hang. systemUnderTest is the viewModel results is the Observable<[T]> that comes back from the service.

let results = systemUnderTest.results.toBlocking()
    let noneObservableList = try! results.single()

    //Then
    XCTAssert(noneObservableList?.count == expectedCount)

It hangs on the try! results.single() and never hits the assert. Anyone know how to test this.

Thanks in advance.

This is systemUnderTest:

public class SearchViewModel: SearchViewModelContract {

public var query: Variable<String?> = Variable(String.EmptyString())
public var results: Observable<[ThirdPartySite]>
let minimumCharacterCount = 4
let dueTime = 0.3
let disposeBag = DisposeBag()

public init() {
    results = Observable.just([Object]())
    results = query.asObservable().throttle(dueTime, scheduler: MainScheduler.instance).flatMapLatest{
        queryString  -> Observable<Object> in

        if let queryString = queryString {
            if queryString.characters.count >= self.minimumCharacterCount {
                return self.Something(siteName: queryString)
            }
            return Observable.just(Object(in: Object()))
        }
        return Observable.just(Object(in: Object()))
        }.map { results in
            return results.items
        }.catchErrorJustReturn([Object]()).shareReplay(1)
    }
}
like image 947
pjapple15 Avatar asked Oct 30 '22 05:10

pjapple15


1 Answers

I have some suggestions:

  • query and results should both be lets, not vars. You should never reset an Observable type. This is part of what it means to be functional.
  • you seem to be using a old version of RxSwift; I suggest you upgrade. - EDIT: DOH! Of course it's an old version of RxSwift, this is an old question!
  • unit testing code that has side effects (the network call) embedded in it can be a huge PITA. Pull up the side effects to a higher level so you can unit test this without it.
  • The debounce operator is a much better fit than throttle for data emissions. The latter works better for triggers.

As for your main question on how to unit test a search, I have found a lot of success with RxTest. Here is code for generating a searchTerm Observable along with a test to prove it works:

extension ObservableType where Element == String? {

    func searchTerm(minCharacterCount: Int = 4, dueTime: RxTimeInterval = .milliseconds(300), scheduler: SchedulerType = MainScheduler.instance) -> Observable<String> {
        return self
            .compactMap { $0 }
            .filter { minCharacterCount <= $0.count }
            .debounce(dueTime, scheduler: scheduler)
    }
}

class Tests: XCTestCase {

    var scheduler: TestScheduler!
    var result: TestableObserver<String>!
    var disposeBag: DisposeBag!

    override func setUp() {
        super.setUp()
        scheduler = TestScheduler(initialClock: 0, resolution: 0.001)
        result = scheduler.createObserver(String.self)
        disposeBag = DisposeBag()
    }

    func testExample() {
        let input = scheduler.createColdObservable([
            .next(1000, Optional.some("")),
            .next(2000, Optional.some("xyz")),
            .next(3000, Optional.some("wxyz")),
            .next(4000, Optional.some("vwxyz")),
            .next(4300, Optional.some("uvwxyz"))
            ])

        input
            .searchTerm(scheduler: scheduler)
            .subscribe(result)
            .disposed(by: disposeBag)

        scheduler.start()

        XCTAssertEqual(result.events, [.next(3300, "wxyz"), .next(4600, "uvwxyz")])
    }
}

The flatMapLatest should go in your side-effecting code, the stuff you don't unit test.

like image 183
Daniel T. Avatar answered Nov 15 '22 06:11

Daniel T.