Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

Android에서 Kotlin Flow 테스트하기

Flow와 통신하는 단위나 모듈을 테스트하는 방법은 테스트 대상이 Flow를 입력으로 사용하는지 또는 출력으로 사용하는지에 따라 다릅니다.

  • 테스트 대상이 흐름을 식별하는 경우에는 테스트에서 제어할 수 있는 모조 종속 항목 내에 흐름을 생성할 수 있습니다.
  • 단위나 모듈이 흐름을 노출하는 경우에는 테스트에서 흐름이 내보내는 항목 하나 이상을 읽고 확인할 수 있습니다.

모조 생산자 만들기

테스트 대상이 흐름의 소비자인 경우 일반적인 테스트 방법은 생산자를 모조 구현으로 대체하는 것입니다. 예를 들어 프로덕션 환경의 두 데이터 소스에서 데이터를 가져오는 저장소를 식별하는 클래스가 있다고 가정해 보겠습니다.

테스트 대상 및 데이터 영역
그림 1. 테스트 대상 및 데이터 영역.

확정적인 테스트를 만들려면 저장소와 종속 항목을 항상 동일한 모조 데이터를 내보내는 모조 저장소로 대체할 수 있습니다.

종속 항목이 모조 구현으로 대체됨
그림 2. 종속 항목이 모조 구현으로 대체됨.

흐름의 사전 정의된 일련의 값을 내보내려면 flow 빌더를 사용합니다.

class MyFakeRepository : MyRepository {
    fun observeCount() = flow {
        emit(ITEM_1)
    }
}

테스트에서 이 모조 저장소를 삽입하여 실제 구현을 대체합니다.

@Test
fun myTest() {
    // Given a class with fake dependencies:
    val sut = MyUnitUnderTest(MyFakeRepository())
    // Trigger and verify
    ...
}

테스트 대상의 출력을 제어할 기능이 있으므로 출력을 확인하여 올바르게 작동하는지 확인할 수 있습니다.

테스트에서 흐름 내보내기 항목 어설션하기

테스트 대상이 주체가 흐름을 노출하는 경우에는 테스트에서 데이터 스트림 요소에 관해 어설션을 만들어야 합니다.

이전 예시의 저장소가 흐름을 노출한다고 가정해 보겠습니다.

흐름을 노출하는 모조 종속 항목이 있는 저장소
그림 3. 흐름을 노출하는 모조 종속 항목이 있는 저장소(테스트 대상).

테스트의 요구에 따라 일반적으로 흐름에서 수신되는 첫 번째 내보내기 또는 적은 수의 한정된 항목을 확인합니다.

first()를 호출하여 첫 번째 항목을 흐름에 사용할 수 있습니다. 다음 함수는 첫 번째 항목이 수신될 때까지 대기한 후 취소 신호를 생산자에게 전송합니다.

@Test
fun myRepositoryTest() = runBlocking {
    // Given a repository that combines values from two data sources:
    val repository = MyRepository(fakeSource1, fakeSource2)

    // When the repository emits a value
    val firstItem = repository.counter.first() // Returns the first item in the flow

    // Then check it's the expected item
    assertThat(firstItem, isEqualTo(ITEM_1) // Using AssertJ
}

테스트에서 여러 값을 확인해야 하는 경우 toList()를 호출하면 흐름이 소스가 모든 값을 내보낼 때까지 대기했다가 이 값을 목록으로 반환합니다. 이 방법은 유한한 데이터 스트림에만 적용됩니다.

@Test
fun myRepositoryTest() = runBlocking {
    // Given a repository with a fake data source that emits ALL_MESSAGES
    val messages = repository.observeChatMessages().toList()

    // When all messages are emitted then they should be ALL_MESSAGES
    assertThat(messages, isEqualTo(ALL_MESSAGES))
}

보다 복잡한 항목 컬렉션이 필요하거나 적은 수의 한정된 항목을 반환하지 않는 데이터 스트림의 경우에는 Flow API를 사용하여 항목을 선택하고 변환할 수 있습니다. 다음은 몇 가지 예입니다.

// Take the second item
outputFlow.drop(1).first()

// Take the first 5 items
outputFlow.take(5).toList()

// Take the first 5 distinct items
outputFlow.take(5).toSet()

// Take the first 2 items matching a predicate
outputFlow.takeWhile(predicate).take(2).toList()

// Take the first item that matches the predicate
outputFlow.firstWhile(predicate)

// Take 5 items and apply a transformation to each
outputFlow.map(transformation).take(5)

// Takes the first item verifying that the flow is closed after that
outputFlow.single()

// Finite data streams
// Verify that the flow emits exactly N elements (optional predicate)
outputFlow.count()
outputFlow.count(predicate)

종속 항목 CoroutineDispatcher

테스트 대상이 종속 항목으로 CoroutineDispatcher를 사용하는 경우에는 kotlinx-coroutines-test 라이브러리TestCoroutineDispatcher 인스턴스를 전달하고 디스패처의 runBlockingTest 메서드 내에서 테스트 본문을 실행하세요.

private val coroutineDispatcher = TestCoroutineDispatcher()
private val uut = MyUnitUnderTest(coroutineDispatcher)

@Test
fun myTest() = coroutineDispatcher.runBlockingTest {
    // Test body
}

추가 Flow 리소스