web-dev-qa-db-ja.com

ユニットテストルームとLiveData

現在、新たに Android Architecture Components を使用してアプリを開発しています。具体的には、クエリの1つでLiveDataobjectを返すルームデータベースを実装しています。挿入とクエリは期待どおりに機能しますが、ユニットテストを使用してクエリメソッドをテストする際に問題があります。

ここに私がテストしようとしている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、または基本的に何にでも変更すると、期待される結果が得られます。データベースに挿入されるデータです。

問題は、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オブジェクトを含む単体テストを実行するより良い方法を知っている人はいますか?

43

観測者がいる場合、Roomは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、あなたはそれを拡張機能にすることができます:)。

47
yigit

ルームのLiveDataからDaoを返すと、クエリasynchronouslyを作成し、@ yigitが言ったように部屋はLiveData#valueLiveDataを観察して、クエリを開始した後の遅延。このパターンは reactive です。

単体テストでは、動作を同期にする必要があるため、テストスレッドをブロックし、値がオブザーバーに渡されるのを待つ必要があります。そこからそれをつかむと、それを主張することができます。

これを行うための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()
27

このような場合、Mockitoが非常に役立つことがわかりました。以下に例を示します。

1。依存関係

testImplementation "org.mockito:mockito-core:2.11.0"
androidTestImplementation "org.mockito:mockito-Android:2.11.0"

2。データベース

@Database(
        version = 1,
        exportSchema = false,
        entities = {Todo.class}
)
public abstract class AppDatabase extends RoomDatabase {
    public abstract TodoDao todoDao();
}

.Dao

@Dao
public interface TodoDao {
    @Insert(onConflict = REPLACE)
    void insert(Todo todo);

    @Query("SELECT * FROM todo")
    LiveData<List<Todo>> selectAll();
}

4。テスト

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

このヘルプを願っています!

12
Hemant Kaushik

@Hemant Kaushikが言ったように、このケースでは[〜#〜] should [〜#〜] use InstantTaskExecutorRule

Developer.Android.comから:

アーキテクチャコンポーネントで使用されるバックグラウンドエグゼキュータを、各タスクを同期的に実行する別のエグゼキュータと交換するJUnitテストルール。

それは実際に動作します!

7
kissed

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 }
1
Josef Raška

JUnit 5を使用している場合、ルールは適用できないため、 この記事 のおかげで、拡張機能を手動で作成できます。

class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {

    override fun beforeEach(context: ExtensionContext?) {
        ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
            override fun executeOnDiskIO(runnable: Runnable) = runnable.run()

            override fun postToMainThread(runnable: Runnable) = runnable.run()

            override fun isMainThread(): Boolean = true
        })
    }

    override fun afterEach(context: ExtensionContext?) {
        ArchTaskExecutor.getInstance().setDelegate(null)
    }
}

そして、テストクラスで次のように使用します。

@ExtendWith(InstantExecutorExtension::class /* , Other extensions */)
class ItemDaoTests {
    ...
}
0
Mahozad