1

We have three states.How can we test(with unit tests) our class which generates random state every 5 seconds, and which can not generate the same state twice in a row? The code of our random generator class is below ` final class StateRandomGenerator: RandomGeneratorProtocol { private var sourceObservable: Disposable? private(set) var previousValue: Int? var generatedValue: PublishSubject = PublishSubject()

init(_ interval: RxTimeInterval,_ scheduler: SchedulerType = MainScheduler.instance) {
    sourceObservable = Observable<Int>
        .interval(interval, scheduler: scheduler)
        .flatMap { [unowned self] _ in self.generateRandom()}
        .compactMap { state in
            return state?.description
        }
        .subscribe(onNext: { [weak self] description in
            self?.generatedValue.onNext(description)
        })
}
func generateRandom() -> Observable<ConnectionState?> {
    return Observable.create { [weak self] observer  in
        var randomNumber = Int.random(in: 0..<ConnectionState.count)
        guard let previousValue = self?.previousValue else {
            let value = ConnectionState(rawValue: randomNumber)
            self?.previousValue = randomNumber
            observer.onNext(value)
            return Disposables.create()
        }
        while randomNumber == previousValue {
            randomNumber = Int.random(in: 0..<ConnectionState.count)
        }
        self?.previousValue = randomNumber
        let value = ConnectionState(rawValue: randomNumber)
        observer.onNext(value)
        
        return Disposables.create()
    }
}
enum ConnectionState: Int {
case error
case connecting
case established

var description: String {
    switch self {
    case .connecting:
        return "It is connecting"
    case .error:
        return "There is an error"
    case .established:
        return "Thе connection is established"
    }
}

} `

1 Answers1

0

You can't successfully unit test your class because it doesn't halt. It just pegs the CPU and chews up memory until the system is finally starved and crashes.

Below is a working and tested Observable that does what you want... The test creates 100,000 ConnectionStates and then checks to ensure that no two adjacent are identical.

The main logic of the function is the closure passed to map which grabs all the cases and filters out the previous case. A random element is chosen from the remainder.

It would be pretty easy to make this generic across any enum I expect. I'll leave that as an exercise for the reader.

func stateRandom(_ interval: RxTimeInterval,_ scheduler: SchedulerType = MainScheduler.instance) -> Observable<ConnectionState> {
    let previous = BehaviorRelay<ConnectionState?>(value: nil)
    return Observable<Int>.interval(interval, scheduler: scheduler)
        .withLatestFrom(previous)
        .map { ConnectionState.allExcept($0) }
        .flatMap { Observable.just($0.randomElement()!) }
        .do(onNext: { previous.accept($0) })
}

extension CaseIterable where Self: Equatable {
    static func allExcept(_ value: Self?) -> [Self] {
        allCases.filter { $0 != value }
    }
}

enum ConnectionState: CaseIterable, Equatable {
    case error
    case connecting
    case established
}

class Tests: XCTestCase {

    func test() throws {
        let scheduler = TestScheduler(initialClock: 0)

        let result = scheduler.start { stateRandom(.seconds(1), scheduler).take(100000) }

        for (prev, current) in zip(result.events, result.events.dropFirst()) {
            XCTAssertNotEqual(prev.value, current.value)
        }
    }
}
Daniel T.
  • 32,821
  • 6
  • 50
  • 72