Unit testing Room and LiveData
As @Hemant Kaushik said, in this case you SHOULD use InstantTaskExecutorRule
.
From developer.android.com:
A JUnit Test Rule that swaps the background executor used by the Architecture Components with a different one which executes each task synchronously.
It really works!
Room calculates the LiveData
's value lazily when there is an observer.
You can check the sample app.
It uses a getValue utility method which adds an observer to get the value:
public static <T> T getOrAwaitValue(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];
}
Better w/ kotlin, you can make it an extensions function :).
I found Mockito is very helpful in such case. Here is an example:
1.Dependencies
testImplementation "org.mockito:mockito-core:2.11.0"
androidTestImplementation "org.mockito:mockito-android:2.11.0"
2.Database
@Database(
version = 1,
exportSchema = false,
entities = {Todo.class}
)
public abstract class AppDatabase extends RoomDatabase {
public abstract TodoDao todoDao();
}
3.Dao
@Dao
public interface TodoDao {
@Insert(onConflict = REPLACE)
void insert(Todo todo);
@Query("SELECT * FROM todo")
LiveData<List<Todo>> selectAll();
}
4.Test
@RunWith(AndroidJUnit4.class)
public class TodoDaoTest {
@Rule
public TestRule rule = new InstantTaskExecutorRule();
private AppDatabase database;
private TodoDao dao;
@Mock
private Observer<List<Todo>> observer;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
Context context = InstrumentationRegistry.getTargetContext();
database = Room.inMemoryDatabaseBuilder(context, AppDatabase.class)
.allowMainThreadQueries().build();
dao = database.todoDao();
}
@After
public void tearDown() throws Exception {
database.close();
}
@Test
public void insert() throws Exception {
// given
Todo todo = new Todo("12345", "Mockito", "Time to learn something new");
dao.selectAll().observeForever(observer);
// when
dao.insert(todo);
// then
verify(observer).onChanged(Collections.singletonList(todo));
}
}
Hope this help!
When you return a LiveData
from a Dao
in Room it makes the query asynchronously, and as @yigit said Room sets the LiveData#value
lazily after you kick off the query by observing the LiveData
. This pattern is reactive.
For unit tests you want the behavior to be synchronous, so you must block the test thread and wait for the value to be passed to the observer, then grab it from there and then you can assert on it.
Here's a Kotlin extension function for doing this:
private fun <T> LiveData<T>.blockingObserve(): T? {
var value: T? = null
val latch = CountDownLatch(1)
val observer = Observer<T> { t ->
value = t
latch.countDown()
}
observeForever(observer)
latch.await(2, TimeUnit.SECONDS)
return value
}
You can use it like this:
val someValue = someDao.getSomeLiveData().blockingObserve()