I’ve got this classes that I would like to test. It is for polling a state from the API until it is successfully activated.
struct Entity {
let id: String
let name: String
let status: EntityStatus
}
extension Entity {
static func sample(id: String = "sample-id-123456",
name: String = "Gorgeous Me",
status: EntityStatus = .allCases.randomElement()!) -> Entity {
return Entity(id: id, name: name, status: status)
}
}
enum EntityStatus: Equatable, CaseIterable {
case unknown
case waiting
case active
}
protocol EntityRepositoryProtocol {
func getEntity(for id: String) -> Observable<Entity>
}
final class EntityRepository: EntityRepositoryProtocol {
private let api: ApiClientProtocol
init(let api: ApiClientProtocol) {
self.api = api
}
func getEntity(for id: String) -> Observable<Entity> {
return api.get(endpoint: "entity", params: "id=\(id)")
.map { EntityResponseModel.mapToEntity($0) }
}
}
final class MockEntityRepository: EntityRepositoryProtocol {
private let getEntityObservable: Observable<Entity>
init(getEntityObservable: Observable<Entity> = .empty()) {
self.getEntityObservable = getEntityObservable
}
func getEntity(for id: String) -> Observable<Entity> {
return getEntityObservable
}
}
typealias ResultEntity = Result<Entity, Error>
protocol EntityPollerProtocol {
func poll(id: String,
per intervalMillis: Int,
for timeoutSeconds: Int) -> Observable<ResultEntity>
}
final class EntityPoller: EntityPollerProtocol {
private let repository: EntityRepositoryProtocol
private let scheduler: SchedulerType
init(repository: EntityRepositoryProtocol,
scheduler: SchedulerType) {
self.repository = repository
self.scheduler = scheduler
}
func poll(id: String,
per intervalMillis: Int,
for timeoutSeconds: Int) -> Observable<ResultEntity> {
let getEntity = Observable<Int>
.interval(.milliseconds(intervalMillis), scheduler: scheduler)
.startWith(0)
.flatMapLatest { [weak self] _ -> Observable<Entity> in
guard let self = self else {
return .never()
}
return self.repository.getEntity(for: id)
}
let timeout = Observable.just(Entity(id: "", name: "", status: .unknown))
.delay(.seconds(timeoutSeconds), scheduler: scheduler)
.map { _ -> Entity in throw ApiError.dataNotFound }
return Observable.merge(getEntity, timeout)
.filter { entity in entity.status != .waiting }
.map { entity in
switch entity.status {
case .active: return ResultEntity.success(entity)
default: return ResultEntity.failure(NSError())
}
}
.catch { error in
return .just(ResultEntity.failure(error))
}
.take(1)
}
}
I have this test that should have worked but the observed event is the one fired next. Not the one I thought will be fired.
func testPoll_ShouldOnlyReturnTrueOnce_WhenStatusActive() throws {
let sampleId = "test-poll-id-123456"
let scheduler = TestScheduler(initialClock: 0, resolution: 0.001)
let sutObserver = scheduler.createObserver(Bool.self)
let entityOutputs = scheduler.createColdObservable([
.next(100, Entity.sample(id: sampleId, status: .waiting)),
.next(200, Entity.sample(id: sampleId, status: .waiting)),
.next(300, Entity.sample(id: sampleId, status: .active)),
.next(400, Entity.sample(id: sampleId, status: .active)),
.next(500, Entity.sample(id: sampleId, status: .active))
]).asObservable()
let bindingInfoRepository = MockEntityRepository(getEntityObservable:entityOutputs)
let sut = EntityPoller(repository: EntityRepositoryProtocol,
scheduler: scheduler)
sut.poll(id: sampleId, per: 100, for: 180000)
.bind(to: sutObserver)
.disposed(by: disposeBag)
scheduler.advanceTo(600)
XCTAssertEqual([.next(300, Entity.sample(id: sampleId, status: .active))), .completed(300)],
sutObserver.events)
}
This should work, however, the event that is fired is the one on the 400 millisecs. So it is off by 1. And it threw this error:
testPoll_ShouldOnlyReturnTrueOnce_WhenStatusActive(): XCTAssertEqual
failed: (“[next(Entity…) @ 300, completed @ 300]”) is not equal to
(“[next(Entity…) @ 400, completed @ 400]”)
Why does this happen? Thanks.




