Единичное тестирование Room и LiveData

В настоящее время я разрабатываю приложение с использованием новых компонентов архитектуры Android . В частности, я реализую базу данных комнат, которая возвращает объект LiveData по одному из своих запросов. Вставка и запрос работают, как ожидалось, однако у меня есть проблема с тестированием метода запроса с помощью модульного теста.

Вот DAO, который я пытаюсь проверить:

NotificationDao.kt

 @Dao interface NotificationDao { @Insert fun insertNotifications(vararg notifications: Notification): List<Long> @Query("SELECT * FROM notifications") fun getNotifications(): LiveData<List<Notification>> 

}

Как вы можете сказать, функция запроса возвращает объект LiveData , если я изменяю это как просто List , Cursor или, в основном, что бы я ни получил ожидаемый результат, который является данными, вставленными в базу данных.

Проблема в том, что следующий тест всегда терпит неудачу, поскольку value объекта LiveData всегда равно null :

NotificationDaoTest.kt

 lateinit var db: SosafeDatabase lateinit var notificationDao: NotificationDao @Before fun setUp() { val context = InstrumentationRegistry.getTargetContext() db = Room.inMemoryDatabaseBuilder(context, SosafeDatabase::class.java).build() notificationDao = db.notificationDao() } @After @Throws(IOException::class) fun tearDown() { db.close() } @Test fun getNotifications_IfNotificationsInserted_ReturnsAListOfNotifications() { val NUMBER_OF_NOTIFICATIONS = 5 val notifications = Array(NUMBER_OF_NOTIFICATIONS, { i -> createTestNotification(i) }) notificationDao.insertNotifications(*notifications) val liveData = notificationDao.getNotifications() val queriedNotifications = liveData.value if (queriedNotifications != null) { assertEquals(queriedNotifications.size, NUMBER_OF_NOTIFICATIONS) } else { fail() } } private fun createTestNotification(id: Int): Notification { //method omitted for brevity } 

Поэтому возникает вопрос: знает ли кто-нибудь о более эффективном способе выполнения модульных тестов, связанных с объектами LiveData?

Комната рассчитывает значение LiveData лениво, когда есть наблюдатель.

Вы можете проверить пример приложения .

Он использует метод полезности getValue, который добавляет наблюдателя для получения значения:

 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]; } 

Лучше w / kotlin, вы можете сделать это функцией расширения :).

Когда вы возвращаете LiveData из Dao в комнате, он делает запрос асинхронно, а как @yigit сказал, что комната устанавливает значение LiveData#value лениво после того, как вы начинаете запрос, наблюдая за LiveData . Эта схема реактивна .

Для модульных тестов вы хотите, чтобы поведение было синхронным, поэтому вы должны блокировать тестовый поток и ждать, пока значение будет передано наблюдателю, а затем возьмите его оттуда, а затем вы сможете утверждать его.

Для этого выполните функцию расширения Kotlin:

 fun <T> LiveData<T>.blockingObserve(): T? { var value: T? = null val latch = CountDownLatch(1) val innerObserver = Observer<T> { value = it latch.countDown() } observeForever(innerObserver) latch.await(2, TimeUnit.SECONDS) return value } 

Вы можете использовать его следующим образом:

 val someValue = someDao.getSomeLiveData().blockingObserve() 

Я обнаружил, что Мокито очень полезен в этом случае. Вот пример:

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)); } } 

Надеюсь, эта помощь!