How to test PublishSubject in RxSwift

Issue #218

Use homemade Recorder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Recorder<T> {
var items = [T]()
let bag = DisposeBag()

func on(arraySubject: PublishSubject<[T]>) {
arraySubject.subscribe(onNext: { value in
self.items = value
}).disposed(by: bag)
}

func on(valueSubject: PublishSubject<T>) {
valueSubject.subscribe(onNext: { value in
self.items.append(value)
}).disposed(by: bag)
}
}

Then test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
final class BookViewModelTests: XCTestCase {
func testBooks() throws {
let expectation = self.expectation(description: #function)
let recorder = Recorder<Book>()
let viewModel = BookViewModel(bookClient: MockBookClient())
recorder.on(arraySubject: viewModel.books)
viewModel.load()

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5, execute: {
expectation.fulfill()
})

wait(for: [expectation], timeout: 2)
XCTAssertEqual(recorder.items.count, 3)
}
}

Need to use great timeout value as DispatchQueue is not guaranteed to be precise, a block needs to wait for the queue to be empty before it can be executed

Make expectation less cumbersome

1
2
3
4
5
6
7
8
9
10
11
extension XCTestCase {
func waitOrFail(timeout: TimeInterval) {
let expectation = self.expectation(description: #function)

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout, execute: {
expectation.fulfill()
})

wait(for: [expectation], timeout: timeout + 2)
}
}

Comments