ozzylee
ozzylee

Reputation: 301

Problem with Android Studio InstrumentedTest with Room

I can't figure out why my instrumented unit test fails at the position indicated in the first code block below. As far as I can see, I've followed the demonstrated cases online for using the Room persistence library, with the test based on the android developer site. Code being tested is derived from the google "room with a view" codelab. The test inserts an object and reads a list of those objects, wrapped as LiveData. The object just inserted should be in the returned list, however null is returned in LiveData.

The test case:

@RunWith(AndroidJUnit4.class)
public class PersonReadWriteTest
{
private PersonDAO personDao;
private EventDatabase database;

@Before
public void createDb ()
{
    Context appContext = InstrumentationRegistry.getTargetContext();
    database = Room.inMemoryDatabaseBuilder(appContext, EventDatabase.class).build();
    personDao = database.personDAO();
}

@After
public void closeDb () throws IOException
{
    database.close();
}

@Test
public void writePersonAndReadInList() throws Exception
{
    Person person = TestUtil.createPerson("John", "Doe");
    personDao.insert(person);
    LiveData<List<Person>> peopleLive = personDao.getAll();
    List<Person> people = peopleLive.getValue();
    assertNotNull(people); <=============================== FAILS HERE
    assertThat(people.size(), equalTo(1));
    Person read = people.get(0);
    assertNotNull(read);
    assertThat(read, equalTo(person));
}
}

Test result:

java.lang.AssertionError
at org.junit.Assert.fail(Assert.java:86)
at org.junit.Assert.assertTrue(Assert.java:41)
at org.junit.Assert.assertNotNull(Assert.java:712)
at org.junit.Assert.assertNotNull(Assert.java:722)
at PersonReadWriteTest.writePersonAndReadInList(PersonReadWriteTest.java:69)
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at android.support.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80)
at android.support.test.internal.runner.junit4.statement.RunAfters.evaluate(RunAfters.java:61)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at android.support.test.runner.AndroidJUnit4.run(AndroidJUnit4.java:101)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:384)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2106)

Person entity:

@Entity(tableName="people")
public class Person implements Serializable
{
// ---------------------------------------------------------------
// Attributes

@PrimaryKey
private Long id = null;
@ColumnInfo(name="first_name")
private String firstName = null;
@ColumnInfo(name="last_name")
private String surname = null;
@ColumnInfo(name="address")
private String address = null;
@ColumnInfo(name="phone1")
private String phone1 = null;
@ColumnInfo(name="phone2")
private String phone2 = null;
@ColumnInfo(name="email")
private String email = null;

// ---------------------------------------------------------------
// Attribute access

... all the public getters and setters...

// -----------------------------------------------------------------

public Person ()
{}
}

DAO:

@Dao
public interface PersonDAO
{
@Query("SELECT * FROM people")
LiveData<List<Person>> getAll ();

@Query("SELECT * FROM people WHERE id IN (:ids)")
LiveData<List<Person>> loadAllByIds (int[] ids);

@Query("SELECT * FROM people WHERE first_name LIKE :first AND last_name LIKE :last LIMIT 1")
LiveData<Person> findByName (String first, String last);

@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert (Person person);

@Delete
void delete (Person person);

@Query("DELETE from people")
void deleteAll ();
}

Database class:

@Database(entities = { Event.class, Person.class }, version = 1)
public abstract class EventDatabase extends RoomDatabase
{
public abstract EventDAO eventDAO ();
public abstract PersonDAO personDAO ();

private static volatile EventDatabase INSTANCE;

static EventDatabase getDatabase (final Context context)
{
    if (INSTANCE == null)
    {
        synchronized (EventDatabase.class)
        {
            if (INSTANCE == null)
            {
                INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                        EventDatabase.class, "event_database").
                        allowMainThreadQueries(). // SHOULD NOT BE USED IN PRODUCTION !!!
                        addCallback(sRoomDatabaseCallback).build();
            }
        }
    }
    return INSTANCE;
}

private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback() {
    @Override
    public void onOpen (@NonNull SupportSQLiteDatabase db)
    {
        super.onOpen(db);
        new PopulateDbAsync(INSTANCE).execute();
    }
};

private static class PopulateDbAsync extends AsyncTask<Void, Void, Void>
{
    private final EventDAO m_EventDao;
    private final PersonDAO m_PersonDao;

    PopulateDbAsync (EventDatabase db)
    {
        m_EventDao = db.eventDAO();
        m_PersonDao = db.personDAO();
    }

    @Override
    protected Void doInBackground (final Void... params)
    {
        Event event;
        m_EventDao.deleteAll();
        m_PersonDao.deleteAll();

        ... insert objects ...
        For example:
        Person person = new Person("Joe", "Bloe");
        m_PersonDao.insert(person);

        return null;
    }
}

}

Extract from app gradle file dependencies:

testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support:support-annotations:27.1.1'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

// Lifecycle components
def archLifecycleVersion = "1.1.0"
implementation "android.arch.lifecycle:extensions:$archLifecycleVersion"
annotationProcessor "android.arch.lifecycle:compiler:$archLifecycleVersion"
implementation "android.arch.lifecycle:viewmodel:$archLifecycleVersion"
implementation "android.arch.lifecycle:livedata:$archLifecycleVersion"

// Room components
def room_version = "1.1.1"
implementation "android.arch.persistence.room:runtime:$room_version"
annotationProcessor "android.arch.persistence.room:compiler:$room_version" // use kapt for Kotlin
androidTestImplementation "android.arch.persistence.room:testing:$room_version"

Upvotes: 2

Views: 1873

Answers (1)

alediaferia
alediaferia

Reputation: 2617

in order to synchronously check for LiveData contents you need to take advantage of an InstantTaskExecutorRule instance as well as observing your LiveData instance for changes.

You can do so by registering an observer.

I recently blogged about a nice solution here.

Upvotes: 1

Related Questions