Android androidTest 中的 LiveData returns 空

Android LiveData in androidTest returns null

我是 运行 androidTest 仪器测试,我有一个方法可以使用 Room returns 来自 DAO 对象的 LiveData。

我是这样调用方法的:

val animal = roomDatabase.animalsDao().getAnimal(1)
animal.observeForever(mMockObserver)
assertNotNull(animal.value)

我用 Mockito 来模拟观察者:

@Mock
private lateinit var mMockObserver = Observer<Animal>

这应该 return 包含 ID 为 1 的动物的 LiveData 实例,但它为空。据我了解,为了让 LiveData return 任何东西,必须有一个观察者。我是不是设置错了?

注意:如果我将 DAO 中的 getAnimal() 的签名直接更改为 return 动物,而不是 LiveData,那么它会起作用,所以我知道它与 LiveData 有关。

经过更多挖掘后,我发现了一个实用方法 Google,通过他们在 GitHub 上的架构组件示例提供。

LiveDataTestUtil

public class LiveDataTestUtil {

    /**
     * Get the value from a LiveData object. We're waiting for LiveData to emit, for 2 seconds.
     * Once we got a notification via onChanged, we stop observing.
     */
    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];
    }
}

这允许您传递 LiveData 实例并取回它持有的值。

更新(JUnit 4):

您还可以使用 InstantTaskExecutorRule combined with observeForever 来测试您的 LiveData。在 Kotlin 中,您在测试 class 的顶部设置 @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() 以确保同步处理 LiveData,然后在您的测试用例中 myLiveData.observeForever { /* Do something when event emitted */ } 获取 LiveData 值。

更新 (JUnit 5)

如果您使用的是 JUnit5,则可以使用此扩展来代替上述 更新 (JUnit4) 中解释的规则。

class InstantTaskExecutorExtension : 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 {
                return true
            }
        })
    }

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

通过像这样注释您的测试 class 使用此扩展:

@ExtendWith(InstantTaskExecutorExtension::class)
class MyTestClass { ... }

如果您不熟悉扩展(它们取代了 JUnit 4 规则),您可以在此处找到其他文档:https://junit.org/junit5/docs/current/user-guide/#extensions

如果你正在做 Kotlin,而不是 Java,那么你也可以使用:

import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

// Original Java: https://github.com/googlesamples/android-architecture-components/blob/master/BasicSample/app/src/androidTest/java/com/example/android/persistence/LiveDataTestUtil.java

object LiveDataTestUtil {
    /**
     * Get the value from a LiveData object. We're waiting for LiveData to emit, for 2 seconds.
     * Once we got a notification via onChanged, we stop observing.
     */
    @Throws(InterruptedException::class)
    fun <T> getValue(liveData: LiveData<T>): T? {
        val data = arrayOfNulls<Any>(1)
        val latch = CountDownLatch(1)
        val observer: Observer<T?> = object : Observer<T?> {
            override fun onChanged(o: T?) {
                data[0] = o
                latch.countDown()
                liveData.removeObserver(this)
            }
        }
        liveData.observeForever(observer)
        latch.await(2, TimeUnit.SECONDS)
        @Suppress("UNCHECKED_CAST")
        return data[0] as T?
    }
}

(目前 A/S 将 Java 自动迁移到 Kotlin 的功能对于 Google class 不能正常工作)