suspend fun test1(): User {
  val profile = repo.getUserProfile()
  val friends = repo.getFriends()
  return User(profile, friends)
}

suspend fun test2(): User {
  val profile = async { repo.getUserProfile() }
  val friends = async { repo.getFriends() }
  return User(profile.await(), friends.await())
}

Untitled

TestCoroutineScheduler

kotlinx-coroutines-testTestCoroutineScheduler는 delay를 가상 시간동안 진행하여 테스트 시 기다리지 않도록 해준다.

아래에서 소개할 advanceTimeBy, runCurrent, advanceUntilIdle과 같은 함수를 제공한다.

fun main() {
  val scheduler = TestCoroutineScheduler()
  println(scheduler.currentTime)  // 0
  scheduler.advanceTimeBy(1000)
  println(scheduler.currentTime)  // 1000
  scheduler.advanceTimeBy(1000)
  println(scheduler.currentTime)  // 2000
}

StandardTestDispatcher

TestCoroutineScheduler를 사용하기 위해 보통 StandardTestDispatcher를 사용한다. 이 디스패처로 시작된 코루틴은 가상 시간만큼 진행되기 전까지 실행되지 않는다.

즉, runTest에서 테스트 코드를 래핑하면 기본 정지 함수를 테스트할 수 있고 코루틴의 지연을 자동으로 건너뛴다. 따라서 delay 함수나 기타 지연이 생기지 않는다.

private val oneDay = 86400000L

@ExperimentalCoroutinesApi
@Test
fun `기본 테스트 디스패처는 코루틴 스코프를 가장 마지막에 실행한다`() = runTest {
	println("test start")
	launch {
    	delay(oneDay)
        println("launch 1")
    }

	async {
    	println("launch 2")
    }

	println("test end")
}

Untitled

위 테스트를 실행해보면 delay가 있음에도 불구하고 테스트는 바로 종료된다. 결과도 test end가 launch보다 먼저 출력되는 것을 볼 수 있다.

따라서 다음 코드는 원하는 대로 동작을 하지 않는다.

@Test
fun standardTest() = runTest {
    val userRepo = UserRepository()

    launch { userRepo.register("Alice") }
    launch { userRepo.register("Bob") }

    assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers())  // ❌ Fails
}

assertEquals이 먼저 실행되기 때문에 검증 전 코루틴 스코프를 실행시켜야 한다.