Nic*_*sco 44 android unit-testing kotlin android-room android-architecture-components
我目前正在使用新的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对象,如果我将其更改为a 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对象的单元测试?
yig*_*git 48
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,你可以使它成为扩展功能:).
Chr*_*rry 27
当你LiveData从一个Dao房间里返回一个它时,它会异步地进行查询,并且@yigit表示LiveData#value在你通过观察开始查询后,房间懒得设置LiveData.这种模式是反应性的.
对于单元测试,您希望行为是同步的,因此您必须阻止测试线程并等待值传递给观察者,然后从那里获取它然后您可以断言它.
这是一个用于执行此操作的Kotlin扩展函数:
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
}
你可以像这样使用它:
val someValue = someDao.getSomeLiveData().blockingObserve()
Hem*_*hik 13
我发现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));
    }
}
希望这有帮助!
正如@Hemant Kaushik所说,在这种情况下你应该使用InstantTaskExecutorRule.
来自developer.android.com:
一个JUnit测试规则,它将架构组件使用的后台执行程序与另一个同步执行每个任务的后台执行程序交换.
真的行!
小智 5
与其他答案略有不同的方法可能是使用https://github.com/jraska/livedata-testing。
您可以避免模拟,并且测试可以使用类似于 RxJava 测试的 API,并且您还可以从 Kotlin 扩展功能中获益。
NotificationDaoTest.kt
val liveData = notificationDao.getNotifications()
liveData.test()
    .awaitValue() // for the case where we need to wait for async data
    .assertValue { it.size == NUMBER_OF_NOTIFICATIONS }
| 归档时间: | 
 | 
| 查看次数: | 11291 次 | 
| 最近记录: |