이번 기사에서는 비동기 코드를 mockk을 이용해 유닛테스트 하는 방법에 대해 설명하겠습니다.
- suspend 함수 모킹하는 방법
- flow 모킹하는 방법
- coEvery{} 와 coVerify{} 사용 방법
- 좋은 연습과 일반적인 함정
suspend 함수 모킹하기
왜 suspend 함수를 모킹해야하나요?
suspend 함수는 코틀린의 코루틴 기반 비동기 프로그래밍의 핵심입니다. 코드가 suspend 함수에 의존하는 경우, 로직을 독립적으로 테스트 하기 위해 모킹해야합니다.
suspend 함수 모킹 좋은 예제
suspend 함수를 사용하는 UserRepository가 있다고 가정해보겠습니다.
class UserRepository {
suspend fun getUser(id: Int): String {
delay(1000) // Simulate a network call
return "John Doe"
}
}
이제 이 함수가 호출되는 ViewModel 테스트 코드를 작성하세요.
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
val userName = MutableLiveData<String>()
fun loadUser(userId: Int) {
viewModelScope.launch {
val name = userRepository.getUser(userId)
userName.value = name
}
}
}
coEvery{}와 coVerify{} 를 사용한 모킹
테스트에서 suspend 함수를 모킹하는 방법입니다.
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
class UserViewModelTest {
@get:Rule
val rule = InstantTaskExecutorRule()
@Test
fun `loadUser updates userName LiveData`() = runTest {
// Arrange
val mockRepository = mockk<UserRepository>()
val viewModel = UserViewModel(mockRepository)
// Stub the suspend function
coEvery { mockRepository.getUser(1) } returns "John Doe"
// Act
viewModel.loadUser(1)
// Assert
coVerify { mockRepository.getUser(1) }
assertEquals("John Doe", viewModel.userName.value)
}
}
핵심
- coEvery{} : suspend 함수를 모킹할때 사용합니다
- coVerify{}: suspend 함수가 호출되었는지 검증합니다
- runTest: 코루틴 코드를 순차적으로 테스트할수 있게 도와줍니다.
flows 모킹하기
왜 flows를 모킹해야하나요?
flow는 데이터를 비동기적으로 처리할 수 있는 좋은 도구입니다. flow에 의존하는 코드를 테스트하면 데이터 스트림이 예상대로 동작하는지 확인할 수 있습니다.
Flow를 리턴하는 레포지토리 예제
유저 이름을 flow로 리턴하는 UserRepository 가 있다고 가정해보겠습니다.
class UserRepository {
fun getUserFlow(): Flow<String> = flow {
emit("John Doe")
delay(1000)
emit("Jane Doe")
}
}
flowWith을 사용한 flow 모킹
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.flow.toList
import org.junit.Assert.assertEquals
import org.junit.Test
class UserRepositoryTest {
@Test
fun `getUserFlow emits correct values`() = runTest {
// Arrange
val mockRepository = mockk<UserRepository>()
coEvery { mockRepository.getUserFlow() } returns flowOf("John Doe", "Jane Doe")
// Act
val result = mockRepository.getUserFlow().toList()
// Assert
assertEquals(listOf("John Doe", "Jane Doe"), result)
}
핵심
- flowOf는 flow 모킹을 쉽게 생성해줍니다.
- toList는 flow 정렬을 쉽게 방출하게해줍니다.
coEvery{}와 coVerify{} 사용
coEvery{}와 coVerify를 언제사용하는가
- coEVery{}는 suspend 함수를 스터빙해주고, flow를 리턴하게해주는 함수입니다.
- coVerify{}는 테스트 중에 suspend 함수가 호출되었는지 확인합니다.
suspend 함수와 flows 결합 예제
suspend 함수와 flow가 있는 레포지토리가 있다고 가정해보겠습니다.
class UserRepository {
suspend fun getUser(id: Int): String = "John Doe"
fun getUserFlow(): Flow<String> = flowOf("John Doe", "Jane Doe")
}
통합 테스트
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Test
class UserRepositoryCombinedTest {
@Test
fun `test both suspend function and Flow`() = runTest {
// Mock repository
val mockRepository = mockk<UserRepository>()
// Stub suspend function
coEvery { mockRepository.getUser(1) } returns "John Doe"
// Stub Flow
coEvery { mockRepository.getUserFlow() } returns flowOf("Jane Doe")
// Test suspend function
val userName = mockRepository.getUser(1)
assertEquals("John Doe", userName)
// Test Flow
val flowResult = mockRepository.getUserFlow().toList()
assertEquals(listOf("Jane Doe"), flowResult)
// Verify the calls
coVerify { mockRepository.getUser(1) }
coVerify { mockRepository.getUserFlow() }
}
}
모범사례와 기본 함정
모범사례
- runTest의 사용은 코루틴 기본 테스트 시 불안정성을 피하게해줍니다.
- mock은 최대한 단순하게 유지하세요: 스터빙을 통해 과한 컴파일링을 피하고, 많은 로직을 테스트하는걸 피하세요.
- 인터렉션 검증: coVerify 사용은 너의 suspend 함수가 기대대로 호출되었는지 검증하게 해줍니다.
- flow 모킹: 간단한 시나리오에서는 flowOf를 사용하고, 더 복잡할 경우에는 flow를 사용하세요.
기본 함정
1. InstantTaskExecutorRule을 사용하지 않는것: 이것은 스레드 문제 때문에 라이브데이터 테스트가 실패할 수도 있습니다.
2. coVerify 잊어버리기: 검증이 없다면, suspend 함수가 호출되었는지 놓칠수 있습니다.
3. 메인스레드 차단: 코루틴을 올바르게 처리하기 위해 runTest를 사용해야합니다.
4. 모킹되지 않은 의존성: 항상 의존성을 모킹하여, 테스트 대상을 격리해야합니다.
결론
- coEvery{}와 coVerify{}를 사용한 suspend 함수 모킹방법
- flowOf와 toList를 사용한 플로우 테스트 방법
- 비동기 코드를 핸들링 할때에 모범사례와 기본 함정
이 기술을 통해 주요하고 안전한 테스트 방법을 마스터했습니다.
기타
pitfalls 함정
isolation 격리
flaskiness 불안정한
'영어 데일리' 카테고리의 다른 글
7가지의 알아야할 코틀린 flow 연산자 (25.01.14) (1) | 2025.01.14 |
---|---|
mockk을 활용한 유지보수와 읽기 쉬운 테스트 코드 작성법 (24.01.12) (0) | 2025.01.12 |
mockk을 이용한 안드로이드 구성요소 단위 테스트 (25.01.10) (0) | 2025.01.10 |
mockk을 활용한 고급 모킹 기법 (25.01.09) (0) | 2025.01.09 |
Mockk와 Mocking 기본 소개 (24.01.08) (1) | 2025.01.08 |