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 주입으로 제어 가능하게 만들기
함수 내부에서 새로운 코루틴을 시작하면, 테스트에서 언제 끝나는지 알 수 없음
class NotificationSender {
fun send() {
GlobalScope.launch { // 새 코루틴 시작
delay(1000)
sendNotification()
}
}
}
@Test
fun test() = runTest {
sender.send()
// 언제 끝날지 모름!
assertEquals(true, notificationSent) // 실패할 수 있음
}
TestScope를 주입하면 advanceUntilIdle()
로 완료를 기다릴 수 있음
해결: TestScope 주입
class NotificationSender(private val scope: CoroutineScope) {
fun send() {
scope.launch { /* 비동기 작업 */ } // 새 코루틴 시작
}
}
@Test
fun `알림 전송 완료 확인`() = runTest {
val sender = NotificationSender(this) // TestScope 주입
sender.send()
advanceUntilIdle() // 모든 코루틴 완료 대기
// 이제 결과 검증 가능
}