iOS

[iOS] RxSwift + Swift Testing

양밀루 2025. 7. 31. 14:51

✅ 왜 RxBlocking이 필요할까? 

RxSwift는 비동기 스트림을 다루는 라이브러리입니다. 하지만 테스트는 동기적으로 결과를 확인해야 합니다

// ❌ 이렇게 하면 안 됨 - 비동기라서 테스트가 끝나기 전에 완료됨
func test_잘못된_방법() {
    let result = userRepository.getUser(id: 1) // Single<User>
    // result는 아직 실행되지 않은 스트림일 뿐!
    #expect(result == expectedUser) // 컴파일 에러!
}

// ✅ RxBlocking으로 동기화
func test_올바른_방법() throws {
    let result = try userRepository.getUser(id: 1)
        .toBlocking()    // 비동기 → 동기 변환
        .single()        // 결과 추출
    
    #expect(result.name == "홍길동")
}

 

 Swift Testing vs XCTest 비교

import XCTest

class UserRepositoryTests: XCTestCase {
    var sut: UserRepository!
    
    override func setUp() {
        sut = UserRepository()
    }
    
    func testGetUser() {
        // Given
        let userId = 1
        
        // When
        let result = try! sut.getUser(id: userId).toBlocking().single()
        
        // Then
        XCTAssertEqual(result.name, "양밀루")
        XCTAssertEqual(result.age, 472)
    }
}
import Testing

@Suite("사용자 Repository 테스트")
struct UserRepositoryTests {
    
    @Test("사용자 조회가 성공하는지 테스트")
    func getUser_성공() throws {
        // Given
        let repository = UserRepository()
        let userId = 1
        
        // When
        let result = try repository.getUser(id: userId)
            .toBlocking()
            .single()
        
        // Then
        #expect(result.name == "양밀루")
        #expect(result.age == 472)
    }
}

 

주요 차이점:

  • class → struct
  • XCTAssert* → #expect
  • func test* → @Test
  • setUp/tearDown 불필요 (struct라서)

 

 RxBlocking 메서드들

1. single() - 하나의 값을 기대할 때

// API 호출 결과 하나 받기
let user = try repository.getUser(id: 1)
    .toBlocking()
    .single()  // Single<User> → User

2. first() - 첫 번째 값만 필요할 때

// 스트림에서 첫 번째 값만
let firstMessage = try chatRepository.getMessages()
    .toBlocking()
    .first()  // Observable<[Message]> → [Message]?

3. toArray() - 모든 값을 배열로

// 모든 이벤트를 배열로 수집
let allEvents = try eventStream
    .take(3)    // 3개만 받고
    .toBlocking()
    .toArray()  // [Event]

 

실제 활용 예시

// 💡 타임아웃 설정 (느린 테스트 방지)
let result = try service.getData()
    .toBlocking(timeout: 5.0)  // 5초 타임아웃
    .single()

// 💡 에러 타입도 정확히 검증
#expect(throws: NetworkError.self) {
    try service.failingMethod().toBlocking().single()
}

// 💡 옵셔널 처리
let maybeResult = try service.getMaybeData()
    .toBlocking()
    .single()

if let result = maybeResult {
    #expect(result.isValid)
} else {
    #expect(Bool(false), "결과가 nil이면 안 됨")
}