Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android testing - start with clean database for every test

Tags:

sqlite

I'm testing my application with Android Instrumentation tests.

So I have a test-class extending ActivityInstrumentationTestCase2 which contains multiple tests. The code looks like this:

public class ManageProjectsActivityTestextends ActivityInstrumentationTestCase2<ManageProjectsActivity> {
    public ManageProjectsActivityTest() {
        super("eu.vranckaert.worktime", ManageProjectsActivity.class);
    }

    @Override
    protected void setUp() throws Exception {
        getInstrumentation().getTargetContext().deleteDatabase(DaoConstants.DATABASE);
        super.setUp();
        solo = new Solo(getInstrumentation(), getActivity());
    }

    @Override
    protected void runTest() throws Throwable {
        super.runTest();
        getActivity().finish();
    }

    public void testDefaults() {
        // My test stuff
    }

    public void testAddProject() {
        // My test stuff
    }
}

So the activity which is under test has a list of projects. The list of projects is retrieved from the database. And when no database is available, so when the DB is created, I insert one default project.

So that means when the tests are run this is what I exepct:

  1. The database, if available, is removed on the device
  2. The first test is started (and thus the activity is launched which creates my DB with one project)
  3. The tests uses the newly created DB, so meaning with only one project, during the test a second project is created
  4. The first test is finished and the setUp() method is called again
  5. The database, that should exist now, is again removed
  6. The second test is started (and thus the activity is launched which creates my DB with one project)
  7. The second test also finishes

But that's not quite what this test-suite does... This is the result of my test-suite:

  1. The database, if available, is removed on the device
  2. The first test is started (and thus the activity is launched which creates my DB with one project)
  3. The tests uses the newly created DB, so meaning with only one project, during the test a second project is created
  4. The first test is finished and the setUp() method is called again
  5. The database, that should exist now, is again removed
  6. And here it comes: The second test is started (but my DB is not created again!!! I cannot see a file on the device either...) and the test should display only one project at the beginning but it does display already two!!!
  7. The second test also finishes but fails because I have two projects at the beginning...

At the beginning I did not override the runTest() method but I thought that maybe I should end the activity myself to force the re-creation, but it doesn't make any difference.

So it seems that the DB is kept in memory (as even no new DB file is created on the device when I explicitly remove it). Or even the activity, because when putting a breakpoint a in the onCreate of the activity I only get in there once for both tests.

For the maintaining the DB I use ORMLite. You can see my helper class here: http://code.google.com/p/worktime/source/browse/trunk/android-app/src/eu/vranckaert/worktime/dao/utils/DatabaseHelper.java

So my question is how to force the tests to use a different DB all the time...?

like image 926
dirkvranckaert Avatar asked Mar 21 '12 07:03

dirkvranckaert


3 Answers

A bit tangential to this problem, but I landed here when I was looking for help. Might be helpful to some folks. If you initialize your database with the RenamingDelegatingContext, it cleans the database between runs.

public class DataManagerTest extends InstrumentationTestCase {

  private DataManager subject;

  @Before
  public void setUp() {
    super.setUp();

    RenamingDelegatingContext newContext = new RenamingDelegatingContext(getInstrumentation().getContext(), "test_");
    subject = new DataManager(newContext);
  }

  // tests...
}

And the associated DataManagerClass.

public class DataManager {

    private SQLiteDatabase mDatabase;
    private SQLiteOpenHelper mHelper;
    private final String mDatabaseName = "table";
    private final int mDatabaseVersion = 1;

    protected DataManager(Context context) {
    this.mContext = context;
    createHelper();
  }

  private void createHelper() {
    mHelper = new SQLiteOpenHelper(mContext, mDatabaseName, null, mDatabaseVersion) {
      @Override
      public void onCreate(SQLiteDatabase sqLiteDatabase) {
        // createTable...
      }

      @Override
      public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        // upgrade table
      }
    };
  }
    ...
}
like image 67
Moemars Avatar answered Nov 15 '22 03:11

Moemars


mDb.delete(DATABASE_TABLE_NAME, null, null); 

That is indeed the solution/way-to-go...

I changed the first line in my setUp(..) method to this:

cleanUpDatabase(tableList);

And then I added the method cleanUpDatabse(..) liek this:

private void cleanUpDatabase(List<String> dbTables) {
    Log.i(LOG_TAG, "Preparing to clean up database...");
    DatabaseHelper dbHelper = new DatabaseHelper(getInstrumentation().getTargetContext());
    ConnectionSource cs = dbHelper.getConnectionSource();
    SQLiteDatabase db = dbHelper.getWritableDatabase();

    Log.i(LOG_TAG, "Dropping all tables");
    for (String table : dbTables) {
        db.execSQL("DROP TABLE IF EXISTS " + table);
    }

    Log.i(LOG_TAG, "Executing the onCreate(..)");
    dbHelper.onCreate(db, cs);

    Log.i(LOG_TAG, "Verifying the data...");
    for (String table : dbTables) {
        Cursor c = db.query(table, new String[]{"id"}, null, null, null, null, null);
        int count = c.getCount();
        if (count != 1 && (table.equals("project") || table.equals("task"))) {
            dbHelper.close();
            Log.e(LOG_TAG, "We should have 1 record for table " + table + " after cleanup but we found " + count + " record(s)");
            throw new RuntimeException("Error during cleanup of DB, exactly one record should be present for table " + table + " but we found " + count + " record(s)");
        } else if (count != 0 && !(table.equals("project") || table.equals("task"))) {
            dbHelper.close();
            Log.e(LOG_TAG, "We should have 0 records for table " + table + " after cleanup but we found " + count + " record(s)");
            throw new RuntimeException("Error during cleanup of DB, no records should be present for table " + table + " but we found " + count + " record(s)");
        }
    }

    Log.i(LOG_TAG, "The database has been cleaned!");
    dbHelper.close();
}

This piece of code gets executed before every test, which makes all my tests independent from each other.

Caution: In order to retrieve a reference to your DatabaseHelper (your own implementation off course ;) ) you cannot call getActivity() because that will launch your activity (and thus do all your initial DB loading (if any..)

like image 39
dirkvranckaert Avatar answered Nov 15 '22 03:11

dirkvranckaert


InstrumentationRegistry → getContext → deleteDatabase

android.support.test.InstrumentationRegistry's getTargetContext and, counterintuitively perhaps, getContext should do the trick:


Synopsis

Use getContext for deleting database (not getTargetContext).

getContext().deleteDatabase(DbHelper.DATABASE_NAME);


Example

public class DbHelperTest {
private DbHelper mDb;

    @Before
    public void setUp() throws Exception {
        getContext().deleteDatabase(DbHelper.DATABASE_NAME);
        mDb = new DbHelper(getTargetContext());
    }

    @After
    public void tearDown() throws Exception {
        mDb.close();
    }

    @Test
    public void onCreate() throws Exception {
        mDb.onCreate(mDb.getWritableDatabase());
    }

    @Test
    public void onUpgrade() throws Exception {
        mDb.onUpgrade(mDb.getWritableDatabase(), 1, 2);
    }

    @Test
    public void dropTable() throws Exception {
        String tableName = "mesa";
        mDb.getReadableDatabase().execSQL("CREATE TABLE "
            + tableName + "(_id INTEGER PRIMARY KEY AUTOINCREMENT)");
        mDb.dropTable(mDb.getWritableDatabase(), tableName);
    }
}
like image 5
Andrew Siplas Avatar answered Nov 15 '22 02:11

Andrew Siplas