๐ก์ฐ์ํํ ํฌ์ฝ์ค ๊ณผ์ ์์ ์งํํ LiveData๋ฅผ ํฌํจํ ํ ์คํธ์ ๋ํ์ฌ ๊ธฐ๋กํ์์ต๋๋ค.
LiveData Testing
- ์๋๋ก์ด๋์ LiveData๋ Android์์ UI์ ๋ฐ์ดํฐ ์ํ๋ฅผ ๊ด์ฐฐํ ์ ์๋๋ก ์ค๊ณ๋ ๊ตฌ์กฐ์ ๋๋ค.
- LiveData๋ Lifecycle์ ์์กดํ๊ธฐ ๋๋ฌธ์ ์ง์ ํ ์คํธํ๊ธฐ์๋ ๋ณต์กํ ์ ์์ผ๋ฉฐ, ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ ๋ผ์ดํ์ฌ์ดํด์ ์๋์ผ๋ก ์กฐ์ํ๊ฑฐ๋ JUnit๊ณผ ํจ๊ป ํ ์คํธ ๋๊ตฌ๋ฅผ ํ์ฉํด์ผ ํฉ๋๋ค.
JUnit4 +InstantTaskExecutorRule
- LiveData๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋์์ ๋์ํฉ๋๋ค.
- InstantTaskExecutorRule๋ฅผ ํ์ฉํ๋ฉด ์ด๋ฅผ ํ ์คํธ ํ๊ฒฝ์์ ์ ์ด ๊ฐ๋ฅํ๊ฒ ํ๋ฉฐ, ์ค๋ ๋ ๋ฌธ์ ๋ฅผ ํํผํ ์ ์์ต๋๋ค.
- InstantTaskExecutorRule์ JVM ํ ์คํธ ํ๊ฒฝ์์ Android Main Thread๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ์ค์ ํด์ค๋๋ค.
Gradle
testImplementation("androidx.arch.core:core-testing:2.2.0")
JUnit4 - Rule
class MainViewModelTest {
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
@Test
fun testLiveData() {
// Given
// LiveData ์์ฑ
val liveData = MutableLiveData<String>()
val observer = Observer<String> {}
// When
liveData.observeForever(observer)
liveData.value = "Hello, World!"
// Then
assertEquals("Hello, World!", liveData.value)
liveData.removeObserver(observer)
}
}
Junit5 + InstantTaskExecutorExtension
- JUnit5์์๋ JUnit 4์ Rule ์ ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก, ์ง์ ์ ์ํด์ผ ํฉ๋๋ค.
InstantTaskExecutorExtension
- Callback์ ์์๋ฐ์ ๊ตฌํํ๋ฉฐ, ๋ฉ์ธ ์ค๋ ๋์์ ๋์ํ๋๋ก ๊ตฌํํฉ๋๋ค.
class InstantTaskExecutorExtension : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) {
runnable.run()
}
override fun postToMainThread(runnable: Runnable) {
runnable.run()
}
override fun isMainThread(): Boolean {
return true
}
})
}
override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
}
}
in Test
- ExtendWith์ ํ์ฉํ์ฌ ํ ์คํธ ํ๊ฒฝ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค.
@ExtendWith(InstantTaskExecutorExtension::class)
class MainViewModelTest
getOrAwaitValue
- LiveData์ ๋ฐ์ดํฐ๋ฅผ ์ป์ด์ค๊ธฐ ์ํด ํด๋น ๊ฐ์ฒด๋ฅผ ๊ตฌ๋ ํ์ฌ ๋ณ๊ฒฝ์ฌํญ์ ์ฒดํฌํ ์ ์์ต๋๋ค.
- getOrAwaitValue๋ LiveData ๊ฐ์ ๋น๋๊ธฐ์ ์ผ๋ก ๊ธฐ๋ค๋ ธ๋ค๊ฐ ํ ์คํธํ ๋ ์ฌ์ฉ๋ฉ๋๋ค.
- getOrAwaitValue ํ์ฅ ํจ์๋ฅผ ํตํด LiveData ๊ฐ์ ๊ด์ฐฐํ๊ณ , ๊ทธ ๊ฐ์ด ์ค์ ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆด ์ ์์ต๋๋ค.
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS
): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(t: T?) {
data = t
latch.countDown()
this@getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}
@Suppress("UNCHECKED_CAST")
return data as T
}
in Test
- ๋น๋๊ธฐ์ ์ผ๋ก ๊ฐ์ ๊ธฐ๋ค๋ ธ๋ค๊ฐ ํ ์คํธํ ์ ์์ต๋๋ค.
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.MutableLiveData
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
class LiveDataTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun testLiveData_withGetOrAwaitValue() {
val liveData = MutableLiveData<String>()
liveData.value = "Hello, World!"
val value = liveData.getOrAwaitValue()
assertEquals("Hello, World!", value)
}
}
Robolectric
- Robolectric๋ JVM ๋ด์์ ์๋ฎฌ๋ ์ด์
๋ ์๋๋ก์ด๋ ํ๊ฒฝ์ ์ ๊ณตํฉ๋๋ค.
- ์๋๋ก์ด๋ ์์กด ํ ์คํธ๋ JVM ํ๊ฒฝ์์ ์คํํ ์ ์์ต๋๋ค.
- Robolectric๋ฅผ ํ์ฉํ๋ฉด LiveData์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์ฝ๊ฒ ์๋ฎฌ๋ ์ด์ ํ ์ ์์ต๋๋ค.
- ์ค์ LiveData๊ฐ ์๋ช ์ฃผ๊ธฐ์ ๋ฐ๋ผ ์ด๋ป๊ฒ ๋์ํ๋์ง ํ์ธ์ด ๊ฐ๋ฅํ๋ค๋ ์ฅ์ ์ด ์์ต๋๋ค.
in Test
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleRegistry
import org.junit.Test
import org.junit.Assert.*
import org.junit.Before
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.junit.runner.RunWith
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class LiveDataRobolectricTest {
private lateinit var lifecycleOwner: LifecycleOwner
private lateinit var lifecycle: LifecycleRegistry
@Before
fun setup() {
lifecycleOwner = LifecycleOwner { lifecycle }
lifecycle = LifecycleRegistry(lifecycleOwner)
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
@Test
fun testLiveData_withRobolectric() {
val liveData = MutableLiveData<String>()
val observer = Observer<String> {}
liveData.observe(lifecycleOwner, observer)
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
liveData.value = "Hello, Robolectric!"
assertEquals("Hello, Robolectric!", liveData.value)
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}
}