seoft

koin repository 대상 간단한 unit test (with Rx) 본문

android

koin repository 대상 간단한 unit test (with Rx)

seoft 2021. 5. 22. 15:30

사이드 프로젝트 진행 중 간단하게 Repository 대상의 테스트가 진행되면 좋을 것 같아 진행하였고, 관련해서 기술합니다.

 

단순히 최근검색어를 로컬로 get, add, clear 하는 로직을 테스트하기위해 필요한 부분중 핵심적인 일부 코드만 모았습니다.

 

 

 

 

먼저 유닛테스트와 디펜던시가 없는 실 프로젝트 구성 중 koin 인잭션이 있는 코드들을 기술합니다.

 

SettingRepository.kt

DataSource와 Preference 주입도 내부적으로 포함되있으나 해당 코드들은 생략하고 unit test로 사용하지 않는 코드들도 생략합니다.

 

class SettingRepository(private val settingLocalDataSource: SettingLocalDataSource) {
// 중략
fun getLatelySearchKeyword(): Single<List<String>> {
return Single.just(settingLocalDataSource.getLatelySearchKeyword())
}
fun addLatelySearchKeyword(keyword: String): Completable {
settingLocalDataSource.addLatelySearchKeyword(keyword)
return Completable.complete()
}
fun clearLatelySearchKeyword(): Completable {
settingLocalDataSource.clearLatelySearchKeyword()
return Completable.complete()
}
}

 

ResultModule.kt

실제 프로젝트 구성에서 전반적인 di를 구성합니다, 해당 예제에서는 repositoryModule, localModule 만 관여되며, SettingRepository 경우 repositoryModule 내에서 주입이이뤄집니다.

 

val resultModule = listOf(
viewModelModule,
networkModule,
repositoryModule,
localModule,
etcModule
)
view raw ResultModule.kt hosted with ❤ by GitHub

 

App.kt

프로젝트에서 koin을 초기화 하는 과정입니다. 

 

class App : Application() {
// 중략
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger(Level.ERROR)
androidContext(applicationContext)
modules(resultModule)
}
}
}
view raw App.kt hosted with ❤ by GitHub

 

 

 

 

위의 프로젝트 구성에서 단순히 유닛테스트 코드 추가만으로 테스트를 하고자 합니다. 다음 두 코드는 위 코드와 별개로 테스트과정을 위해서 추가됩니다.

 

RxSchedulerRule.kt

프로젝트에 지정된 스케줄러를 사용 할 경우 지정된 스케줄러에 따라 테스트와 무관한 스케줄러로 돌아가 의도치 않은 결과 혹은 테스트종료 상황이 야기될 수 있습니다. 이를 방지하고자 테스트에서 사용되는 모든 rx스케줄러를 trampoline 스케줄러로 지정하기 위해 필요한 Rule입니다.

 

import io.reactivex.android.plugins.RxAndroidPlugins
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.schedulers.Schedulers
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
class RxSchedulerRule : TestRule {
override fun apply(base: Statement?, description: Description?): Statement {
return object : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setSingleSchedulerHandler { Schedulers.trampoline() }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
try {
base?.evaluate()
} finally {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
}
}
}
}
}

 

MyTest.kt

테스트가 구동되는 코드입니다.

 

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import io.reactivex.internal.functions.Functions
import io.reactivex.rxkotlin.subscribeBy
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.koin.test.KoinTest
import org.koin.test.inject
@RunWith(AndroidJUnit4::class)
class MyTest : KoinTest {
val settingRepository by inject<SettingRepository>()
val appContext by lazy { InstrumentationRegistry.getInstrumentation().targetContext } // not used here
@Rule
@JvmField
val rule = RxSchedulerRule()
@Test
fun testLatelySearchKeyword() {
settingRepository.clearLatelySearchKeyword().subscribe(Functions.EMPTY_ACTION)
settingRepository.getLatelySearchKeyword().subscribeBy(onSuccess = {
Assert.assertEquals(0, it.size)
})
settingRepository.addLatelySearchKeyword("111").subscribe(Functions.EMPTY_ACTION)
settingRepository.addLatelySearchKeyword("222").subscribe(Functions.EMPTY_ACTION)
settingRepository.addLatelySearchKeyword("333").subscribe(Functions.EMPTY_ACTION)
settingRepository.addLatelySearchKeyword("444").subscribe(Functions.EMPTY_ACTION)
settingRepository.addLatelySearchKeyword("111").subscribe(Functions.EMPTY_ACTION)
settingRepository.getLatelySearchKeyword().subscribeBy(onSuccess = {
Assert.assertEquals(listOf("111", "444", "333", "222"), it)
})
settingRepository.clearLatelySearchKeyword().subscribe(Functions.EMPTY_ACTION)
settingRepository.getLatelySearchKeyword().subscribeBy(onSuccess = {
Assert.assertEquals(0, it.size)
})
}
}
view raw MyTest.kt hosted with ❤ by GitHub

 

추가사항 : 

비교적 복잡한 Mock 을 구성하는 방식이 아닌 단순히 AVD 를 대상으로 진행되는 테스트이다 보니까 디바이스의 해당 패키지명으로 구성된 앱의 환경을 따르게 되며 테스트 전 이미 구성되있는 로컬구성 등의 환경 상황을 고려하며 테스트 코드를 작성해야합니다.

Comments