Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- UITextView
- 리팩토링
- 리펙토링
- RxCocoa
- ios
- Human interface guide
- SWIFT
- Protocol
- Xcode
- ribs
- rxswift
- map
- Refactoring
- 스위프트
- 클린 코드
- tableView
- swift documentation
- clean architecture
- uiscrollview
- Clean Code
- Observable
- HIG
- collectionview
- UICollectionView
- 리펙터링
- uitableview
- swiftUI
- 애니메이션
- combine
- MVVM
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - Swift] XCTest로 비동기 테스트 방법 (RxSwift 테스트 방법) 본문
테스트 대상
- 플러스 버튼을 누르면 1초 이후에 값이 증가하는 앱
- 테스트: 버튼 누름 -> 1초 후에 값이 변화하는지 확인
- 샘플 프로젝트에서 사용한 라이브러리 - MVVM을 템플릿화 해놓은 ReactorKit 사용
target 'ExAsyncRx' do
use_frameworks!
pod 'ReactorKit'
pod 'RxCocoa'
target 'ExAsyncRxTests' do
inherit! :search_paths
end
end
- 예제에 사용될 ViewController
import UIKit
import ReactorKit
import RxSwift
import RxCocoa
class ViewController: UIViewController, ReactorKit.View {
private let plusButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("plus", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private let label: UILabel = {
let label = UILabel()
label.text = "0"
label.font = .systemFont(ofSize: 30, weight: .bold)
label.numberOfLines = 1
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
[plusButton, label]
.forEach(view.addSubview(_:))
NSLayoutConstraint.activate([
plusButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
plusButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
NSLayoutConstraint.activate([
label.leftAnchor.constraint(equalTo: plusButton.rightAnchor, constant: 16),
label.centerYAnchor.constraint(equalTo: plusButton.centerYAnchor),
])
}
func bind(reactor: ViewReactor) {
// Action
plusButton.rx.tap
.map(Reactor.Action.tapPlusButton)
.bind(to: reactor.action)
.disposed(by: disposeBag)
// State
reactor.state.map(\.cnt)
.observe(on: MainScheduler.asyncInstance)
.map(String.init)
.bind(to: label.rx.text)
.disposed(by: disposeBag)
}
}
- 예제에 사용할 ViewReactor
import ReactorKit
final class ViewReactor: Reactor {
enum Action {
case tapPlusButton
}
enum Mutation {
case setPlusCnt(Int)
}
struct State {
var cnt = 0
}
let initialState = State()
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .tapPlusButton:
return .just(.setPlusCnt(1))
.delay(.seconds(1), scheduler: MainScheduler.asyncInstance)
}
}
func reduce(state: State, mutation: Mutation) -> State {
var state = state
switch mutation {
case let .setPlusCnt(int):
state.cnt += int
}
return state
}
}
테스트 코드 작성
- @testable로 ViewController, ViewReactor가 있는 모듈 임포트
- 테스트에 사용될 XCTest와 RxSwift 임포트
- 아래 코드 준비
// ExAsyncRxTests.swift
@testable import ExAsyncRx
import XCTest
import RxSwift
final class ExAsyncRxTests: XCTestCase {
var disposeBag: DisposeBag!
var sut: ViewReactor!
override func setUp() {
}
func test_WhenTapPlusButton_ThenPlusCount() {
}
}
- setup에서 기본적인 인스턴스 생성
override func setUp() {
disposeBag = DisposeBag()
sut = ViewReactor()
}
- 1) Given
- reactor와 expectation 준비
func test_WhenTapPlusButton_ThenPlusCount() {
// Given
let reactor = sut!
let expectation = XCTestExpectation(description: "test_WhenTapPlusButton_ThenPlusCount")
}
- 2) When
- reactor의 tapPlusButton 액션을 인풋
// When - tap plus button
reactor.action.onNext(.tapPlusButton)
- 3) Then
- reactor의 cnt 상태를 구독하고 있다가 값이 변경되었을때 1로 제대로 변경되는지 확인
- 비동기이므로 XCTest의 wait(for:)를 이용하여 테스트
// Then - cnt is one
reactor.state.map(\.cnt)
.subscribe { item in
guard item == 1 else { return }
expectation.fulfill()
}
.disposed(by: disposeBag)
wait(for: [expectation], timeout: 3)
(테스트 부분 전체 코드)
@testable import ExAsyncRx
import XCTest
import RxSwift
final class ExAsyncRxTests: XCTestCase {
var disposeBag: DisposeBag!
var sut: ViewReactor!
override func setUp() {
disposeBag = DisposeBag()
sut = ViewReactor()
}
func test_WhenTapPlusButton_ThenPlusCount() {
// Given
let reactor = sut!
let expectation = XCTestExpectation(description: "test_WhenTapPlusButton_ThenPlusCount")
// When - tap plus button
reactor.action.onNext(.tapPlusButton)
// Then - cnt is one
reactor.state.map(\.cnt)
.subscribe { item in
guard item == 1 else { return }
expectation.fulfill()
}
.disposed(by: disposeBag)
wait(for: [expectation], timeout: 3)
}
}
'iOS 응용 (swift)' 카테고리의 다른 글
Comments