单元测试室和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对象,如果我改变这只是一个ListCursor或基本上无论如何我得到预期的结果,这是数据库中插入的数据。

问题是,以下测试总是失败,因为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,你可以使它扩展功能:)。

当你从一个房间里的Dao返回一个LiveData ,它会异步地进行查询,并且@yigit表示Room在LiveData#value之后启动查询后懒惰地设置了LiveData#value 。 这种模式是被动的 。

对于单元测试,你希望行为是同步的,所以你必须阻塞测试线程并等待将值传递给观察者,然后从那里抓取它,然后你可以断言它。

这是一个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() 

我发现Mockito在这种情况下非常有帮助。 这里是一个例子:

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

希望这个帮助!