18

Tôi hiện đang phát triển một ứng dụng bằng cách sử dụng mới Android Architecture Components. Cụ thể là tôi đang triển khai Cơ sở dữ liệu phòng trả về một đối tượng LiveData trên một trong các truy vấn của nó. Chèn và truy vấn hoạt động như mong đợi, tuy nhiên tôi gặp vấn đề khi thử nghiệm phương thức truy vấn bằng cách sử dụng kiểm tra đơn vị.Kiểm tra đơn vị Phòng và LiveData

Dưới đây là DAO Tôi đang cố gắng để kiểm tra:

NotificationDao.kt

@Dao 
interface NotificationDao { 

@Insert 
fun insertNotifications(vararg notifications: Notification): List<Long> 

@Query("SELECT * FROM notifications") 
fun getNotifications(): LiveData<List<Notification>> 

}

Như bạn có thể nói, chức năng truy vấn trả về một đối tượng LiveData, nếu Tôi thay đổi điều này để chỉ là một List, Cursor hoặc về cơ bản bất cứ điều gì sau đó tôi nhận được kết quả mong đợi, đó là dữ liệu được chèn vào trong Cơ sở dữ liệu.

Vấn đề là các thử nghiệm sau đây sẽ luôn luôn thất bại vì value của đối tượng LiveData luôn null là:

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 
} 

Vì vậy, câu hỏi là: Có ai biết cách tốt hơn để thực hiện các bài kiểm tra đơn vị có liên quan đến các đối tượng LiveData?

Trả lời

25

Phòng tính giá trị của LiveData khi có người quan sát.

Bạn có thể kiểm tra sample app.

Nó sử dụng một phương pháp getValue tiện ích mà thêm một người quan sát để nhận được giá trị:

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

Better w/Kotlin, bạn có thể làm cho nó một chức năng mở rộng :).

+1

Có bản cập nhật nào trong phiên bản 1.0.0 không? –

10

Khi bạn quay lại một LiveData từ một Dao trong Phong nó làm cho các truy vấn không đồng bộ, và như @yigit nói Room đặt LiveData#value uể oải sau khi bạn khởi truy vấn bằng cách quan sát LiveData. Mẫu này là reactive.

Để kiểm tra đơn vị bạn muốn hành vi đồng bộ, vì vậy bạn phải chặn chuỗi thử và chờ giá trị được chuyển đến người quan sát, sau đó lấy nó từ đó và sau đó bạn có thể khẳng định nó.

Dưới đây là một chức năng mở rộng Kotlin để làm điều này:

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 
} 

Bạn có thể sử dụng nó như thế này:

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

Người ta cũng có thể biến nó thành thuộc tính mở rộng thay thế, nội tuyến người quan sát sử dụng lambda và sử dụng giá trị trả về 'await' để phân biệt giữa dữ liệu rỗng và dữ liệu không được đặt trong giới hạn thời gian: https://gist.github.com/arekolek/e9e0d050cdd6ed16cd7dd9183eee62c0 – arekolek

2

tôi thấy Mockito là rất hữu ích trong trường hợp như vậy. Dưới đây là ví dụ:

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

Hy vọng trợ giúp này!

+0

Nhưng không phải bạn đang thử nghiệm cả hai selectAll và chèn cùng một lúc, ở đây? – Rasive