관리 메뉴

김종권의 iOS 앱 개발 알아가기

[UnitTest] RxSwift, MVVM 구조의 테스트 코드 작성 하기 (RxTest, RxNimble) 본문

Unit Test와 UI Test

[UnitTest] RxSwift, MVVM 구조의 테스트 코드 작성 하기 (RxTest, RxNimble)

jake-kim 2021. 2. 12. 09:32

* 사용되는 라이브러리

- RxSwift

- RxCocoa

- RxTest (RxSwift의 스트림들을 테스트 할 수 있는, 가상 시간을 함께 이벤트를 발생시킬 수 있는 프레임워크)

- RxNimble (expect라는 함수로 명료하게 테스트를 할 수 있게 해주는 프레임워크)

1. 간단한 RxSwift, MVVM구조 설계

RxSwift구조 코드 참고: ios-development.tistory.com/173

2. Unit 테스트 코드 작성

1) Nimble프레임워크에서 사용 할 events함수 정의

import Foundation
import RxSwift
import RxTest
import Nimble
import RxNimble

// Tests그룹 하위에, ExpectationExtensions.swift에 정의
public extension Expectation where T: ObservableConvertibleType {

    internal func transform<U>(_ closure: @escaping (T?) throws -> U?) -> Expectation<U> {
        let exp = expression.cast(closure)
        return Expectation<U>(expression: exp)
    }

    func events(scheduler: TestScheduler,
                disposeBag: DisposeBag,
                startAt initialTime: Int = 0) -> Expectation<[Recorded<Event<T.Element>>]> {
        return transform { source in
            let results = scheduler.createObserver(T.Element.self)

            scheduler.scheduleAt(initialTime) {
                source?.asObservable().subscribe(results).disposed(by: disposeBag)
            }
            scheduler.start()

            return results.events
        }
    }
}

2) Void타입의 Expectation을 비교하기 위해서 함수 정의

import RxSwift
import RxTest
import Nimble
import RxNimble

// Tests그룹 하위에 Operators.swift로 추가
public func equal<Void>(_ expectedValue: Void?) -> Predicate<Void> {
    return Predicate.define("equal <\(stringify(expectedValue))>") { actualExpression, msg in
        let actualValue = try actualExpression.evaluate()
        switch (expectedValue, actualValue) {
        case (nil, _?):
            return PredicateResult(status: .fail, message: msg.appendedBeNilHint())
        case (nil, nil), (_, nil):
            return PredicateResult(status: .fail, message: msg)
        default:
            var isEqual = false

            if String(describing: expectedValue).count != 0, String(describing: expectedValue) == String(describing: actualValue) {
                isEqual = true
            }
            return PredicateResult(bool: isEqual, message: msg)
        }
    }
}

3) 테스트 코드 작성

import XCTest
import RxSwift
import RxCocoa
import RxNimble
import Nimble
import RxTest
@testable import Test

class MyTests: XCTestCase {

    var bag: DisposeBag!
    var scheduler: TestScheduler!
    var viewModel = ToggleVM()
    lazy var output = viewModel.transform(input: .init(didTapBtnToggle: didTapBtnToggle.asObservable()))

    var didTapBtnToggle: PublishSubject<Void>!

    // Given
    override func setUp() {
        bag = DisposeBag()
        scheduler = TestScheduler(initialClock: 0)
        viewModel = ToggleVM()
        didTapBtnToggle = PublishSubject<Void>()
    }

    func testAddCount() throws {
        // When
        scheduler.createColdObservable([
            .next(1, ()),
            .next(10, ()),
            .next(30, ()),
            ]).bind(to: didTapBtnToggle).disposed(by: bag)

        // Then
        expect(self.output.toggleCount).events(scheduler: scheduler, disposeBag: bag).to(equal([
            .next(0, 0),
            .next(1, 1),
            .next(10, 2),
            .next(30, 3),
        ]))
    }
}

* 전체 코드는 여기에 업로드

Comments