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)
}
}
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.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With