Reputation: 66814
I created a TestObserver
class like RxJava counterpart and LiveData extension function to test multiple values of LiveData, such as states like LOADING
and as result of the action ERROR
or SUCCESS
.
class TestObserver<T>(private val liveData: LiveData<T>) : Observer<T> {
init {
liveData.observeForever(this)
}
private val testValues = mutableListOf<T>()
override fun onChanged(t: T) {
if (t != null) testValues.add(t)
}
fun testAssertNoValues(): TestObserver<T> {
if (testValues.isNotEmpty()) throw AssertionException("Assertion error with actual size ${testValues.size}")
return this
}
fun testAssertValueCount(count: Int): TestObserver<T> {
if (count < 0) throw AssertionException("Assert count cannot be smaller than zero")
if (count != testValues.size) throw AssertionException("Assertion error with expected $count while actual ${testValues.size}")
return this
}
fun assertValues(vararg predicates: T): TestObserver<T> {
predicates.forEach { predicate ->
testValues.forEach { testValue ->
if (predicate != testValue) throw Exception("Assertion error")
}
}
return this
}
fun assertValues(predicate: List<T>.() -> Boolean): TestObserver<T> {
testValues.predicate()
return this
}
fun values(predicate: List<T>.() -> Unit): TestObserver<T> {
testValues.predicate()
return this
}
fun values(): List<T> {
return testValues
}
fun dispose() {
testValues.clear()
liveData.removeObserver(this)
}
}
fun <T> LiveData<T>.test(): TestObserver<T> {
val testObserver = TestObserver(this)
observeForever(testObserver)
return testObserver
}
class AssertionException(message: String) : Exception(message)
And using this on a sample test to check if it works
@Test
fun test() = testCoroutineRule.runBlockingTest {
val myTestData = MutableLiveData<Int>()
myTestData.value = 1
myTestData.value = 2
myTestData.value = 3
myTestData.test().values {
this.forEach {
println("🔥 Test numbers: $it")
}
}
}
But mutableList is null i wonder why?
Upvotes: 0
Views: 313
Reputation: 66814
Calling liveData.removeObserver(this)
before observing it solved the issue. And made some changes on full implmentation.
/**
* RxJava style [Observer] for [LiveData] to test multiple values or states in container.
*
* This class is useful for testing view or action states or order of states if you are using
* stateful machine.
*
* * Use with `InstantTaskExecutorRule` or a similar mechanism to execute tasks synchronously.
*
*/
class LiveDataTestObserver<T> constructor(
private val liveData: LiveData<T>
) : Observer<T> {
init {
liveData.observeForever(this)
}
private val testValues = mutableListOf<T>()
override fun onChanged(t: T) {
if (t != null) testValues.add(t)
}
fun assertNoValues(): LiveDataTestObserver<T> {
if (testValues.isNotEmpty()) throw AssertionError(
"Assertion error with actual size ${testValues.size}"
)
return this
}
fun assertValueCount(count: Int): LiveDataTestObserver<T> {
if (count < 0) throw AssertionError(
"Assertion error! value count cannot be smaller than zero"
)
if (count != testValues.size) throw AssertionError(
"Assertion error! with expected $count while actual ${testValues.size}"
)
return this
}
fun assertValues(vararg predicates: T): LiveDataTestObserver<T> {
if (!testValues.containsAll(predicates.asList())) throw AssertionError("Assertion error!")
return this
}
fun assertValues(predicate: (List<T>) -> Boolean): LiveDataTestObserver<T> {
predicate(testValues)
return this
}
fun values(predicate: (List<T>) -> Unit): LiveDataTestObserver<T> {
predicate(testValues)
return this
}
fun values(): List<T> {
return testValues
}
/**
* Removes this observer from the [LiveData] which was observing
*/
fun dispose() {
liveData.removeObserver(this)
}
/**
* Clears data available in this observer and removes this observer from the [LiveData] which was observing
*/
fun clear() {
testValues.clear()
dispose()
}
}
fun <T> LiveData<T>.test(): LiveDataTestObserver<T> {
val testObserver = LiveDataTestObserver(this)
// Remove this testObserver that is added in init block of TestObserver, and clears previous data
testObserver.clear()
observeForever(testObserver)
return testObserver
}
Upvotes: 1