swift

RxSwift UnitTest 해보기(RxTest, RxNimble)

motosw3600 2022. 3. 10. 18:13

프로젝트 목표

  • 프로젝트의 아키텍처는 MVVM패턴에 RxSwift를 적용한 구조(참고)
  • RxTest와 RxNimble을 사용하여 스트림에 테스트 값을 주입하여 expect로 output(Count값)비교

프로젝트 구조

-, +버튼으로 값을 count하는 간단한 프로젝트

ViewModelType 선언

protocol ViewModelType {
    associatedtype Input
    associatedtype Output
    
    func transform(input: Input) -> Output
}

ViewModel 구조

ViewModelType을 채택하는 ViewModel 선언

 

class CountViewModel: ViewModelType {
    
    let disposeBag = DisposeBag()
    
    struct Input {
        var plusBtnTapped: Observable<Void>
        var minusBtnTapped: Observable<Void>
    }
    
    struct Output {
        var countValue: Driver<Int>
    }
    
    func transform(input: Input) -> Output {
        let countValue = BehaviorRelay<Int>(value: 0)
        
        input.plusBtnTapped
            .subscribe(onNext: {
                countValue.accept(countValue.value + 1)
            })
            .disposed(by: disposeBag)
        
        input.minusBtnTapped
            .subscribe(onNext: {
                countValue.accept(countValue.value - 1)
            })
            .disposed(by: disposeBag)
        
        return Output(countValue: countValue.asDriver(onErrorJustReturn: 0))
    }
}

ViewController

input은 plusButton과 minusButton의 rx.tap의 Observable

outout은 viewModel의 transform을 통해 input에 대한 구독을 하고있는 Driver

 

class CountViewController: UIViewController {

    @IBOutlet weak var minusButton: UIButton!
    @IBOutlet weak var plusButton: UIButton!
    @IBOutlet weak var countLabel: UILabel!
    
    let disposeBag = DisposeBag()
    var viewModel = CountViewModel()
    
    lazy var input = CountViewModel.Input(plusBtnTapped: plusButton.rx.tap.asObservable(),
                                          minusBtnTapped: minusButton.rx.tap.asObservable())
    lazy var output = viewModel.transform(input: input)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        bind()
    }
    
    private func bind() {
        output.countValue
            .map { "\($0)" }
            .drive(countLabel.rx.text)
            .disposed(by: disposeBag)
    }

}

 

Test코드 작성해보기

RxTest: scheduler를 사용하여 createColdObservable로 스트림에 emit을 예약하여 테스트 가능

RxNimble: expect를 사용해 XCTAssertEqual을 사용하지 않고 이벤트에 대한 스케듈러에 to(equal())로 값을 비교

 

func testCountValue() {
        // +버튼 Scheduler Observable생성
        scheduler.createColdObservable(
            [
                .next(10, ()),
                .next(20, ()),
                .next(30, ())
            ]
        )
            .bind(to: plusSubject)
            .disposed(by: disposeBag)
        
        // -버튼 Scheduler Observable생성
        scheduler.createColdObservable(
            [
                .next(25, ())
            ]
        )
            .bind(to: minusSubject)
            .disposed(by: disposeBag)
        
        // expect를 통한 스케듈러 값 비교
        expect(self.output.countValue).events(scheduler: scheduler, disposeBag: disposeBag).to(equal(
               [
                   .next(0, 0),
                   .next(10, 1),
                   .next(20, 2),
                   .next(25, 1),
                   .next(30, 2)
               ]
           ))
        
    }

 

 

 

 

참고

https://github.com/sergdort/CleanArchitectureRxSwift

https://github.com/RxSwiftCommunity/RxNimble

https://github.com/ReactiveX/RxSwift/blob/main/Documentation/UnitTests.md

'swift' 카테고리의 다른 글

Custom ScrollPaging  (0) 2022.06.05
RxDataSources  (0) 2022.05.07
Tuist로 프로젝트 관리해보기  (0) 2022.03.05
The Composable Architecture(TCA)  (0) 2022.03.02
Memory Debugging(leak Test)  (0) 2022.02.20