18

我目前正在開發一個應用程序,使用新的Android Architecture Components。具體來說,我正在實現一個房間數據庫,它在其中一個查詢上返回一個LiveData對象。插入和查詢按預期工作,但我有一個問題使用單元測試來測試查詢方法。單元測試室和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對象的value總是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對象的單元測試嗎?

回答

25

當有觀察者時,房間懶洋洋地計算出LiveData的值。您可以檢查sample app

它採用了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 /科特林,你可以把它的擴展功能:)。

+1

版本1.0.0中是否有更新? –

10

當您從Dao房間它使查詢異步,並作爲@yigit說房間設置LiveData#value你通過觀察LiveData揭開序幕後,懶洋洋地查詢返回LiveData。這種模式是reactive

對於單元測試,你希望行爲是同步的,所以你必須阻止測試線程,並等待值傳遞給觀察者,然後從那裏抓取它,然後你可以斷言它。

下面是這樣一個科特林擴展功能:

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() 
+0

也可以將其作爲擴展屬性,使用lambda內聯觀察者,並使用'await'返回值來區分空數據和未在時限內設置的數據:https://gist.github.com/arekolek/e9e0d050cdd6ed16cd7dd9183eee62c0 – arekolek

2

我發現是的Mockito在這種情況下非常有用。這裏是一個例子:

1。依賴

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

希望得到這個幫助!

+0

但是,你不是在同時測試selectAll和insert,在這裏? – Rasive