Reputation: 2310
I'm running an androidTest instrumentation test and I have a method that returns LiveData from a DAO object using Room.
I'm calling the method like so:
val animal = roomDatabase.animalsDao().getAnimal(1)
animal.observeForever(mMockObserver)
assertNotNull(animal.value)
I used Mockito to mock the observer:
@Mock
private lateinit var mMockObserver = Observer<Animal>
This should return an instance of LiveData containing the Animal at id 1, but it's null. It's my understanding that in order for LiveData to return anything, there must be an observer. Did I set this up incorrectly?
Note: If I change the signature of getAnimal() in the DAO to return an Animal directly, rather than a LiveData, then it works so I know it's something with LiveData.
Upvotes: 6
Views: 4765
Reputation: 1
An example for part "Update (JUnit 4)" in Programmer001's answer. (Tuned with official doc on purpose)
Database item data class and table definition:
@Entity(tableName = "item")
data class Item (
var name: String,
@PrimaryKey(autoGenerate = true) var id: Int = 0
)
Test class:
package ...
import ...
@RunWith(AndroidJUnit4::class)
class DBInstrumentedTest1 {
@get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
private lateinit var db: DB
@Before
private fun createDb(): DB {
db = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
DB::class.java
).build()
}
@After
@Throws(IOException::class)
fun closeDb() {
db.close()
}
@Test
@Throws(Exception::class)
fun coroutine_livedata_db_tests_work() {
val itemDao = db.getItemDao()
val item = Item(name = "First", id = 1)
runBlocking(Dispatchers.Default) { itemDao.insert(Item(item.name)) }
itemDao.getItemByName(item.name).asLiveData().observeForever {
assertThat(it, equalTo(item))
}
}
}
Upvotes: 0
Reputation: 2310
After a little more digging I've found a utility method Google provided through their Architecture Components examples on GitHub.
public class 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.
*/
public static <T> T getValue(final LiveData<T> liveData) throws InterruptedException {
final Object[] data = new Object[1];
final CountDownLatch latch = new CountDownLatch(1);
Observer<T> observer = new Observer<T>() {
@Override
public void onChanged(@Nullable T o) {
data[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
liveData.observeForever(observer);
latch.await(2, TimeUnit.SECONDS);
//noinspection unchecked
return (T) data[0];
}
}
This allows you to pass the LiveData instance and get back the value it holds.
Update (JUnit 4):
You can also use the InstantTaskExecutorRule combined with observeForever to test your LiveData. In Kotlin you set @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
at the top of your test class to ensure LiveData is handled synchronously, then inside your test cases myLiveData.observeForever { /* Do something when event emitted */ }
to get the LiveData value.
Update (JUnit 5)
If you're using JUnit5, then you can use this extension instead of the Rule explained in Update (JUnit4) above.
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)
}
}
Use this extension by annotating your test class like so:
@ExtendWith(InstantTaskExecutorExtension::class)
class MyTestClass { ... }
If you're new to extensions (they replace JUnit 4 Rules), you can find additional documentation here: https://junit.org/junit5/docs/current/user-guide/#extensions
Upvotes: 15
Reputation: 384
If you are doing Kotlin, rather than Java, then you can also use:
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
// Original Java: https://github.com/googlesamples/android-architecture-components/blob/master/BasicSample/app/src/androidTest/java/com/example/android/persistence/LiveDataTestUtil.java
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.
*/
@Throws(InterruptedException::class)
fun <T> getValue(liveData: LiveData<T>): T? {
val data = arrayOfNulls<Any>(1)
val latch = CountDownLatch(1)
val observer: Observer<T?> = 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?
}
}
(At the moment the feature of A/S for automigration of Java to Kotlin doesn't quite work correctly for the Google class)
Upvotes: 1