Mock으로 병렬 처리 테스트하기

Mock의 delay로 각 작업 시간을 명시하고, 총 소요 시간으로 병렬 처리 검증

@Test
fun `병렬 처리 확인`() = runTest {
    // Mock 설정 - 각 작업별 소요 시간 명시
    coEvery { userRepo.getName() } coAnswers { delay(600); "name" }
    coEvery { userRepo.getFriends() } coAnswers { delay(800); friends }

    // 실행
    useCase.fetchUserData()

    // 병렬: max(600, 800) = 800ms / 순차: 600 + 800 = 1400ms
    assertEquals(800, currentTime)
}

디스패처를 전환하는 함수 테스트하기

어떤 디스패처에서 실행되고 있는지 테스트

방법 1: 스레드 이름으로 확인

@Test
fun `IO 디스패처 사용 확인`() = runBlocking {
    var threadName: String? = null
    every { csvReader.read(any()) } answers {
        threadName = Thread.currentThread().name // 실행 스레드 기록
        result
    }

    saveReader.readSave("file.csv")

    // IO 디스패처의 스레드 이름은 "DefaultDispatcher-worker-"로 시작
    assertTrue(threadName!!.startsWith("DefaultDispatcher-worker-"))
}

방법 2: 디스패처 주입

// 디스패처를 주입하도록 설계
class UseCase(private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
    suspend fun execute() = withContext(ioDispatcher) { /* 작업 */ }
}

// 테스트할 때 주입해주기
val useCase = UseCase(ioDispatcher = EmptyCoroutineContext)

함수 실행 중 상태 변화 테스트하기

runCurrent()로 단계별 실행, advanceUntilIdle()로 완료까지 진행

@Test 
fun `로딩 상태 변화 확인`() = runTest {
    launch { viewModel.sendData() }           // 1. 코루틴 생성만 함 (아직 실행 안됨)
    assertEquals(false, viewModel.loading.value)  // 2. 초기값 확인
    
    runCurrent()                              // 3. 대기 중인 코루틴들 실행
    assertEquals(true, viewModel.loading.value)   // 4. loading = true 확인
    
    advanceUntilIdle()                        // 5. 모든 작업 완료까지 진행  
    assertEquals(false, viewModel.loading.value)  // 6. loading = false 확인
}

새 코루틴을 시작하는 함수 테스트하기

TestScope 주입으로 제어 가능하게 만들기

해결: TestScope 주입

class NotificationSender(private val scope: CoroutineScope) {
    fun send() {
        scope.launch { /* 비동기 작업 */ }  // 새 코루틴 시작
    }
}

@Test
fun `알림 전송 완료 확인`() = runTest {
    val sender = NotificationSender(this)  // TestScope 주입
    
    sender.send()
    advanceUntilIdle()  // 모든 코루틴 완료 대기
    
    // 이제 결과 검증 가능
}