[iOS] Swift Testing Framework와 RxTest의 스레드 이슈

[iOS] Swift Testing Framework와 RxTest의 스레드 이슈

1. 문제 상황

Swift Testing Framework와 RxTest를 함께 사용하면서 아래와 같은 테스트 코드를 작성했습니다.

@Test("some test")
func test_something() throws {
    let scheduler = TestScheduler(initialClock: 0)

    let coordinateObservable = scheduler.createHotObservable([
        .next(100, 1)
    ])

    // ... 나머지 테스트 코드
}

문제없이 동작할거라 예상했지만, 다음과 같은 에러가 발생했습니다.

Fatal error: Executing on background thread. Please use `MainScheduler.instance.schedule` to schedule work on main thread.

2. 원인 분석

break point를 찍어가며 에러가 발생하는 부분을 추적해보면 다음과 같습니다.

let coordinateObservable = scheduler.createHotObservable([
  .next(100, 1)
]) // 에러 발생


public func scheduleAbsoluteVirtual<StateType>(_ state: StateType, time: VirtualTime, action: @escaping (StateType) -> Disposable) -> Disposable {
  MainScheduler.ensureExecutingOnScheduler() // 에러 발생

  // ...
}

public static func ensureExecutingOnScheduler(errorMessage: String? = nil) {
  if !DispatchQueue.isMain {
      rxFatalError(errorMessage ?? "Executing on background thread. Please use `MainScheduler.instance.schedule` to schedule work on main thread.")
  }
}

이 문제가 발생하는 이유는 Swift Testing Framework가 기본적으로 테스트를 백그라운드 스레드에서 실행하는 반면, TestScheduler는 메인 스레드에서의 실행을 요구하기 때문입니다.

3. 해결 방법

  1. XCTest를 이용한다. 내가 선택한 방법은 다음과 같다.
@MainActor
func test_repository_returns_stores() async {
  // Given
  let coordinate = PublishSubject<CLLocationCoordinate2D>()
  let expectedResults = makeExpectedStores()
  let expectation = expectation(description: "Stores fetched")
  mockRepository.stubbedResult = .success(expectedResults)

  var receivedStores: [StoreEntity]?

  // When
  let input = HomeViewModel.Input(coordinate: coordinate)
  let output = sut.transform(input: input)

  output.stores
    .drive(onNext: { stores in
      receivedStores = stores
      expectation.fulfill()
    })
    .disposed(by: disposeBag)

  coordinate.onNext(makeCoordinate())

  // Then
  await fulfillment(of: [expectation], timeout: 2.0)
  XCTAssertEqual(receivedStores, expectedResults)
}
  1. 기다린다.. 현재 (2024-12.17) 기준으로는 6.8.0이 최신 버전인데 다음 버전에서는 아마 수정되어서 출시가 될 것 같다. (이미 누군가 같은 문제를 겪고 수정함..!)