프로젝트 목표
- 프로젝트의 아키텍처는 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 |