单元测试室和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
或基本上无论如何我得到预期的结果,这是数据库中插入的数据。
问题是,以下测试总是失败,因为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)); } }
希望这个帮助!