apple/RxSwift, ReactorKit

[ReactorKit] ReactorKit 공부하기 #2

lgvv 2022. 7. 24. 02:40

ReactorKit 공부하기 #2

 

해당 문서는 ReactorKit 3.2.0을 기준으로 작성.

이번에는 테스트코드를 작성해볼 예정

 

 

들어가기 전

RxTest는 반드시 CocoaPod에서 Tests쪽으로 타겟을 잡아주기!

 

ㅠㅠ 이걸로 하루 반정도 삽질 ㅠㅠ

 

테스팅

ReactorKit의 경우에는 테스트를 위한 함수가 내장되어 있어서, 손쉽게 view와 reacotr 둘 다 테스트 가능.

 

그렇다면 무엇을 테스트 해야하냐면 총 3가지를 테스트하면 좋음

  • View 
    • Action :: 주어진 유저의 상호작용에 따라 적절한 action이 reactor로 보내지는가?
    • State :: 주어진 state에서 view의 변수가 적절하게 set(저장) 되는가?
  • Reactor
    • State :: action에 따라 state가 적절하게 변경되었는가?

 

ReactorKit testing

 

리액터킷 테스트코드

//
//  ReactorKitPracticeTests.swift
//  ReactorKitPracticeTests
//
//  Created by Hamlit Jason on 2022/07/23.
//

import XCTest
@testable import ReactorKitPractice
import RxTest
import RxSwift

class ReactorKitPracticeTests: XCTestCase {
    
    override func setUp() {
    }
    
    override func tearDownWithError() throws { }
    
    // MARK: - View -> Reactor
    // View에서 Reactor Action을 잘 넘기는지 테스트
    func test_Action_View_to_Reactor() {
        // given
        let reactor = CounterViewReactor()
        let viewController = CounterViewController()
        reactor.isStubEnabled = true
        viewController.reactor = reactor
        
        // when
        viewController.increaseButton.sendActions(for: .touchUpInside)
        XCTAssertEqual(reactor.stub.actions.last, .increase)
        
        // then
        viewController.decreaseButton.sendActions(for: .touchUpInside)
        XCTAssertEqual(reactor.stub.actions.last, .decrease)
    }
    
    // MARK: - Reactor -> View
    // Reactor에서 변경된 상태(State)가 View에도 정상적으로 반영되는지 확인.
    func test_State_Reactor_to_View() {
        // given
        let reactor = CounterViewReactor()
        let viewController = CounterViewController()
        reactor.isStubEnabled = true
        viewController.reactor = reactor
        
        // when
        reactor.stub.state.value = CounterViewReactor.State(value: 0, isLoading: true)
        
        // then
        XCTAssertEqual(viewController.loadingIndicator.isAnimating, true)
        XCTAssertEqual(viewController.countLabel.text, "0")
    }
    
    // MARK: - Reactor
    // action을 받으면 비지니스 로직(Mutation)이 잘 처리되어 State값이 기대값으로 변하는지 확인.
    func test_Reactor() {
        let reactor = CounterViewReactor()
        
        let expectation = XCTestExpectation()
        expectation.expectedFulfillmentCount = 1
        
        // when
        reactor.action.onNext(.increase)
        
        // then
        XCTAssertEqual(reactor.currentState.isLoading, true)
    }
    
}

 

 

 

 

실패한 테스트코드

테스트 코드를 실패했을 때를 기록

  • 문제상황
    • isLoading의 경우에는 false -> true(딜레이 1초) -> false로 결과가 방출되어야할 것 같은데, false -> true만 내려오고 있었음
    • 딜레이 처리를 잘못한 것이 원인이 아닐까 생각하고 있으나, 원인을 잘 모르겠어서 ㅜㅜ 우선 기록
   // 실패한 테스트 코드
   // 작성일: 2022년 7월 25일
   // 원인: delay의 문제로 보이나, 어떻게 해결할지 아직 잘 모르겠음.
   // (문제 해결을 위해 시도해본 방법)
   // 1. DispatchQueue를 활용
   // 2. expectation 활용
   
   func test_isLoading() {
        let scheduler = TestScheduler(initialClock: 0)
        let reactor = CounterViewReactor()
        let disposeBag = DisposeBag()
        
        scheduler.createHotObservable([
            .next(100, .increase),
        ])
            .subscribe(reactor.action)
            .disposed(by: disposeBag)
        
        // then
        let response = scheduler.start(created: 0, subscribed: 0, disposed: 1000) { 
            reactor.state.map(\.isLoading)
        }
        XCTAssertEqual(response.events.map(\.value.element), [ 
            false, // initial state
            true,  // 값 변경 .isLoading
            false  // 딜레이 후 isLoading
        ])
        

    }