Created
November 8, 2021 16:28
-
-
Save ConorGarry/26d4b62e98d17ccb3ff36c1722d3cbca to your computer and use it in GitHub Desktop.
LiveData test extensions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ie.conorgarry.patreontemplate | |
import androidx.annotation.VisibleForTesting | |
import androidx.lifecycle.LiveData | |
import androidx.lifecycle.Observer | |
import java.util.concurrent.CountDownLatch | |
import java.util.concurrent.TimeUnit | |
import java.util.concurrent.TimeoutException | |
// kotlin-coroutines-end/app/src/test/java/com/example/android/kotlincoroutines/LiveDataTestUtil.kt | |
/** | |
* Reminder that this is needed to run LiveData unit tests. | |
* ``` | |
* @get:Rule // Required for LiveData unit test. | |
* public var instantExecutorRule = InstantTaskExecutorRule() | |
* ``` | |
*/ | |
object LiveDataTestUtil { | |
/** | |
* Get the value from a LiveData object. We're waiting for LiveData to emit, for 2 seconds. | |
* Once we got a notification via onChanged, we stop observing. | |
*/ | |
fun <T> getValue(liveData: LiveData<T>): T { | |
val data = arrayOfNulls<Any>(1) | |
val latch = CountDownLatch(1) | |
val observer = object : Observer<T> { | |
override fun onChanged(o: T?) { | |
data[0] = o | |
latch.countDown() | |
liveData.removeObserver(this) | |
} | |
} | |
liveData.observeForever(observer) | |
latch.await(2, TimeUnit.SECONDS) | |
@Suppress("UNCHECKED_CAST") | |
return data[0] as T | |
} | |
} | |
@VisibleForTesting(otherwise = VisibleForTesting.NONE) | |
fun <T> LiveData<T>.getOrAwaitValue( | |
time: Long = 2, | |
timeUnit: TimeUnit = TimeUnit.SECONDS, | |
afterObserve: () -> Unit = {} | |
): T { | |
var data: T? = null | |
val latch = CountDownLatch(1) | |
val observer = object : Observer<T> { | |
override fun onChanged(o: T?) { | |
data = o | |
latch.countDown() | |
[email protected](this) | |
} | |
} | |
this.observeForever(observer) | |
try { | |
afterObserve.invoke() | |
// Don't wait indefinitely if the LiveData is not set. | |
if (!latch.await(time, timeUnit)) { | |
throw TimeoutException("LiveData value was never set.") | |
} | |
} finally { | |
this.removeObserver(observer) | |
} | |
@Suppress("UNCHECKED_CAST") | |
return data as T | |
} | |
class LiveDataValueCapture<T> { | |
private val lock = Any() | |
private val _values = mutableListOf<T?>() | |
val values: List<T?> | |
get() = synchronized(lock) { | |
_values.toList() // copy to avoid returning reference to mutable list | |
} | |
fun addValue(value: T?) = synchronized(lock) { | |
_values += value | |
} | |
} | |
/** | |
* Slightly modified version of [this][https://stackoverflow.com/a/60951065/3429021] | |
* | |
* @param expected Wait for the expected amount of states to happen. | |
* Defaults to 2 as it will most likely either be | |
* a loading state followed by either a success or error. | |
*/ | |
inline fun <T> LiveData<T>.captureValues( | |
expected: Int = 2, | |
block: LiveDataValueCapture<T>.() -> Unit | |
) { | |
val capture = LiveDataValueCapture<T>() | |
val observer = Observer<T> { | |
capture.addValue(it) | |
} | |
observeForever(observer) | |
try { | |
// Wait for all states to happen. Without this, tests using OkHttp MockWebServer require | |
// a Thread.sleep or some other delay to allow for subsequent states. | |
@Suppress("ControlFlowWithEmptyBody", "EmptyWhileBlock") | |
while (capture.values.size < expected) {} | |
capture.block() | |
} finally { | |
removeObserver(observer) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment