샘성의 iOS 개발 일지

ReactorKit Unit Test하기 본문

iOS/ReactiveX

ReactorKit Unit Test하기

SamusesApple 2023. 7. 4. 18:25
728x90

1. 테스트 내용 선정

  오늘 테스트하고자 하는 것은 하단의 3가지이다.

  • View의 Action이 Reactor에게 잘 전달 되었는지 확인
  • Reactor가 전달받은 Action에 대한 Mutation을 잘 작동시켰는지 State 상태 확인
  • Reactor의 State를 View가 잘 구독하고 있는지 확인

 

2. 테스트 방식

  테스트 방식은

  1. SUT 설정
  2. 테스트 시나리오 작성 및 실행 (GWT 형식)

  순서로 진행될 것이다.
  (만약 이 내용이 이해가지 않는다면 하단의 게시글 참고 부탁드립니다.)

 

[XCTest] Unit Test작성하기 (feat. RxSwift)

1. Unit Test란? 가장 작은 단위의 테스트로, 앱의 동작을 위해 작성한 코드(기능) 1개가 의도대로 잘 작동하는지 검증하는 것을 목적으로 둔다. 2. Unit Test의 필요성? 물론, Unit Test를 하지 않고 시뮬

iossammy.tistory.com

 

 

3. 테스트 대상 선정 및 시나리오 작성

  우선, View와 Reactor간의 상태 전달이 잘 일어나는지를 확인하는 것이므로 테스트 대상은 ViewController로 설정하게 된다.

let sut: ViewController()

 

 

하단의 3가지를 테스트할 것이기에 시나리오를 최소 3가지 이상은 작성하게 된다.

  • View의 Action이 Reactor에게 잘 전달 되었는지 확인
  • Reactor가 전달받은 Action에 대한 Mutation을 잘 작동시켰는지 State 상태 확인
  • Reactor의 State를 View가 잘 구독하고 있는지 확인

  필자는 depositButton, withdrawButton에 대한 액션이 잘 작동하는지를 확인해야하기에

  View의 Action 전달에 대한 2가지 시나리오, Mutation에 대한 State 변화 확인을 위한 2가지 시나리오, 그리고 State에 대한 View의 상태 변경 여부에 대한 1가지 시나리오   >>  이렇게 총 5가지 시나리오를 작성할 것이다.

 

  그리고 각 시나리오에 대한 코드는 하단의 코드와 같다.

 

* View -> Reactor Action Test
    
    func testAction_whenDidTapDepositButton_amount300_sendActions() {
        // Given
        let reactor = TransactionReactor(data: mockData)
        reactor.isStubEnabled = true
        sut.reactor = reactor
        sut.amonutTextField.text = "300"
        
        // When
        sut.depositButton.sendActions(for: .touchUpInside)
        
        // Then
        >> reactor에서 마지막으로 동작하는 액션이 오른쪽과 같은지 확인
        XCTAssertEqual(reactor.stub.actions.last, .deposit(300))
    }
    
    func testAction_whenDidTapWithdrawButton_amount300_sendActions() {
        // Given
        let reactor = TransactionReactor(data: mockData)
        reactor.isStubEnabled = true
        sut.reactor = reactor
        sut.amonutTextField.text = "300"
        
        // When
        sut.withdrawButton.sendActions(for: .touchUpInside)
        
        // Then
        >> reactor에서 마지막으로 동작하는 액션이 오른쪽과 같은지 확인
        XCTAssertEqual(reactor.stub.actions.last, .withdraw(300)) 
    }

 

* Reactor (State status about Mutation) Test
    
    func testStateStatus_whenDeposit300_thenBalanceStateIs300() {
        // Given
        let reactor = TransactionReactor(data: mockData)
        
        // When
        reactor.action.onNext(.deposit(300))
        
        // Then
        >> 변화한 reactor의 State가 예상값과 일치한지 확인
        XCTAssertEqual(reactor.currentState.currentBalance, 300)
    }
    
    func testStateStatus_whenWIthdraw300_thenBalanceStateIsMinus300() {
        // Given
        let reactor = TransactionReactor(data: mockData)
        
        // When
        reactor.action.onNext(.withdraw(300))
        
        // Then
        >> 변화한 reactor의 State가 예상값과 일치한지 확인
        XCTAssertEqual(reactor.currentState.currentBalance, -300)
    }

 

* Reactor -> View (View observing State Value) Test
	func testState_whenStateValueIs300_thenTextFieldTextIs300() {
        // Given
        let reactor = TransactionReactor(data: mockData)
        reactor.isStubEnabled = true
        
        sut.reactor = reactor
        
        // When
        reactor.stub.state.value = .init(currentBalance: 300)
        
        // Then
        >> State값이 제대로 바인딩 되는지 확인
        let testBalance = sut.balanceView.balanceLabel.text
        XCTAssertEqual(testBalance, "300")
    }

 

 

 

 

4. 전체 코드

import XCTest
@testable import RxSwift_Tutorial_6
import ReactorKit

final class RxSwift_Tutorial_6Tests: XCTestCase {

    private var sut: TransactionViewController!
    private var mockData = MockData(account: BankAccount(balance: 0,
                                                         history: []))
    
    override func setUp() {
        sut = TransactionViewController()
    }
    
    override func tearDown() {
        sut = nil
    }
    
    // MARK: - View -> Reactor Action Test
    
    func testAction_whenDidTapDepositButton_amount300_sendActions() {
        // Given
        let reactor = TransactionReactor(data: mockData)
        reactor.isStubEnabled = true
        sut.reactor = reactor
        sut.amonutTextField.text = "300"
        
        // When
        sut.depositButton.sendActions(for: .touchUpInside)
        
        // Then
        XCTAssertEqual(reactor.stub.actions.last, .deposit(300))
    }
    
    func testAction_whenDidTapWithdrawButton_amount300_sendActions() {
        // Given
        let reactor = TransactionReactor(data: mockData)
        reactor.isStubEnabled = true
        sut.reactor = reactor
        sut.amonutTextField.text = "300"
        
        // When
        sut.withdrawButton.sendActions(for: .touchUpInside)
        
        // Then
        XCTAssertEqual(reactor.stub.actions.last, .withdraw(300))
    }
    
    // MARK: - Reactor (State status about Mutation) Test
    
    func testStateStatus_whenDeposit300_thenBalanceStateIs300() {
        // Given
        let reactor = TransactionReactor(data: mockData)
        
        // When
        reactor.action.onNext(.deposit(300))
        
        // Then
        XCTAssertEqual(reactor.currentState.currentBalance, 300)
    }
    
    func testStateStatus_whenWIthdraw300_thenBalanceStateIsMinus300() {
        // Given
        let reactor = TransactionReactor(data: mockData)
        
        // When
        reactor.action.onNext(.withdraw(300))
        
        // Then
        XCTAssertEqual(reactor.currentState.currentBalance, -300)
    }
    
    // MARK: - Reactor -> View (View observing State Value) Test
    
    func testState_whenStateValueIs300_thenTextFieldTextIs300() {
        // Given
        let reactor = TransactionReactor(data: mockData)
        reactor.isStubEnabled = true
        
        sut.reactor = reactor
        
        // When
        reactor.stub.state.value = .init(currentBalance: 300)
        
        // Then
        let testBalance = sut.balanceView.balanceLabel.text
        XCTAssertEqual(testBalance, "300")
    }
}
728x90