Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android LiveData in androidTest returns null

I'm running an androidTest instrumentation test and I have a method that returns LiveData from a DAO object using Room.

I'm calling the method like so:

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

I used Mockito to mock the observer:

@Mock
private lateinit var mMockObserver = Observer<Animal>

This should return an instance of LiveData containing the Animal at id 1, but it's null. It's my understanding that in order for LiveData to return anything, there must be an observer. Did I set this up incorrectly?

Note: If I change the signature of getAnimal() in the DAO to return an Animal directly, rather than a LiveData, then it works so I know it's something with LiveData.

like image 735
Programmer001 Avatar asked Feb 03 '18 23:02

Programmer001


2 Answers

After a little more digging I've found a utility method Google provided through their Architecture Components examples on 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];
    }
}

This allows you to pass the LiveData instance and get back the value it holds.

Update (JUnit 4):

You can also use the InstantTaskExecutorRule combined with observeForever to test your LiveData. In Kotlin you set @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() at the top of your test class to ensure LiveData is handled synchronously, then inside your test cases myLiveData.observeForever { /* Do something when event emitted */ } to get the LiveData value.

Update (JUnit 5)

If you're using JUnit5, then you can use this extension instead of the Rule explained in Update (JUnit4) above.

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

Use this extension by annotating your test class like so:

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

If you're new to extensions (they replace JUnit 4 Rules), you can find additional documentation here: https://junit.org/junit5/docs/current/user-guide/#extensions

like image 58
Programmer001 Avatar answered Nov 09 '22 14:11

Programmer001


If you are doing Kotlin, rather than Java, then you can also use:

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?
    }
}

(At the moment the feature of A/S for automigration of Java to Kotlin doesn't quite work correctly for the Google class)

like image 42
IainCunningham Avatar answered Nov 09 '22 13:11

IainCunningham