Testing database on Android: ProviderTestCase2 or RenamingDelegatingContext? Testing database on Android: ProviderTestCase2 or RenamingDelegatingContext? database database

Testing database on Android: ProviderTestCase2 or RenamingDelegatingContext?


Both the ProviderTestCase and RenamingDelegatingContext will destroy the database if one already exists before opening it within it's context, so in that sense they both have the same low-level approach towards opening a SQLite database.

You leverage this by opening the database in your fixture in setUp(), which will then ensure that your working with a fresh database before each test case.

I would suggest that you go for writing content providers rather than creating database adapters. You can use a common interface for accessing data, be it stored in the DB or somewhere over the network, the design of content providers can be accommodated to access such data at the cost of a bit of IPC overhead involved that most of us shouldn't have to care about.

If you did this for accessing a SQLite database, the framework would completely manage the database connection for you in a separate process. As added beef, the ProviderTestCase2<ContentProvider> completely bootstraps a test context for your content provider without you having to a write a single line of code.

But, that's not said it isn't such a huge effort to do the bootstrapping yourself. So supposing you had a database adapter as such; we'll just focus on open() for getting write access to our database, nothing fancy:

public class MyAdapter {    private static final String DATABASE_NAME = "my.db";    private static final String DATABASE_TABLE = "table";    private static final int DATABASE_VERSION = 1;    /**     * Database queries     */    private static final String DATABASE_CREATE_STATEMENT = "some awesome create statement";    private final Context mCtx;    private SQLiteDatabase mDb;    private DatabaseHelper mDbHelper;    private static class DatabaseHelper extends SQLiteOpenHelper {        public DatabaseHelper(Context context) {            super(context, DATABASE_NAME, null, DATABASE_VERSION);        }        @Override        public void onCreate(SQLiteDatabase db) {            db.execSQL(DATABASE_CREATE_STATEMENT);          }        @Override        public void onUpgrade(SQLiteDatabase db, int a, int b) {            // here to enable this code to compile        }    }    /**     * Constructor - takes the provided context to allow for the database to be     * opened/created.     *      * @param context the Context within which to work.     */    public MyAdapter(Context context) {        mCtx = context;    }    /**        * Open the last.fm database. If it cannot be opened, try to create a new        * instance of the database. If it cannot be created, throw an exception to        * signal the failure.        *         * @return this (self reference, allowing this to be chained in an        *         initialization call)        * @throws SQLException if the database could be neither opened or created        */    public MyAdapter open() throws SQLException {        mDbHelper = new DatabaseHelper(mCtx);        mDb = mDbHelper.getWritableDatabase();        return this;    }    public void close() {            mDbHelper.close();        }}

Then you could write your test as such:

public final class MyAdapterTests extends AndroidTestCase {    private static final String TEST_FILE_PREFIX = "test_";private MyAdapter mMyAdapter;@Overrideprotected void setUp() throws Exception {    super.setUp();    RenamingDelegatingContext context         = new RenamingDelegatingContext(getContext(), TEST_FILE_PREFIX);    mMyAdapter = new MyAdapter(context);    mMyAdapter.open();}@Overrideprotected void tearDown() throws Exception {    super.tearDown();    mMyAdapter.close();    mMyAdapter = null;}public void testPreConditions() {    assertNotNull(mMyAdapter);}}

So what's happening here is that the context implementation of RenamingDelegatingContext, once MyAdapter(context).open() is called, will always recreate the database. Each test you write now will be going against the state of the database after MyAdapter.DATABASE_CREATE_STATEMENT is called.


I actually use database with SQLiteOpenHelper and I have a trick for testing.The idea is to use standard on-file stored DB during the normal use of the app and an in-memory DB during tests. In this way you can use a clear DB for each test without insert/delete/update data in your standard DB. It works fine for me.

Keep in mind you can use in-memory database, just passing null as name of database file. This is clearly documented in the API documentation.

Advantages of using in-memory DB during tests is explained here:https://attakornw.wordpress.com/2012/02/25/using-in-memory-sqlite-database-in-android-tests/

In my project I have the DBHelper class wich extends SQLiteHelper. As you can see, there are the standard methods. I simply added a constructor with two parameters. The difference is that when I call the super constructor, I pass null as DB name.

public class DBHelper extends SQLiteOpenHelper {    public static final int DATABASE_VERSION = 1;    public static final String DATABASE_NAME = "mydatabase.db";    public DBHelper(Context context) {        super(context, DATABASE_NAME, null, DATABASE_VERSION);    }    public DBHelper(Context context, boolean testMode) {        super(context, null, null, DATABASE_VERSION);    }    public void onCreate(SQLiteDatabase db) {        //create statements    }    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {        //on upgrade policy    }    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {        //on downgrade policy    }}

Every "model" in the project extends DBModel that is an abstract class.

public abstract class DBModel {    protected DBHelper dbhelper;    public DBModel(Context context) {        dbhelper = new DBHelper(context);    }    //other declarations and utility function omitted}

As discussed here: How can I find out if code is running inside a JUnit test or not?there is a way to establish if you are running JUnit tests, simply searching in stack trace elements.As a conseguence, I modified DBModel constructor

public abstract class DBModel {    protected DBHelper dbhelper;    public DBModel(Context context) {        if(isJUnitTest()) {            dbhelper = new DBHelper(context, true);        } else {            dbhelper = new DBHelper(context);        }    }    private boolean isJUnitTest() {        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();        List<StackTraceElement> list = Arrays.asList(stackTrace);        for (StackTraceElement element : list) {            if (element.getClassName().startsWith("junit.")) {                return true;            }        }        return false;    }    //other declarations and utility function omitted}

Note that

startsWith("junit.")

may be

startsWith("org.junit.")

in your case.


I have an application that uses a ContentProvider backed by an sqlite database to provide data to the application.

Let PodcastDataProvider be the actual dataprovider used by the application.

Then you can set up a test provider with something like the following:

public abstract class AbstractPodcastDataProvider extends ProviderTestCase2<PodcastDataProvider>{    public AbstractPodcastDataProvider(){        this(PodcastDataProvider.class, Feed.BASE_AUTH);    }    public AbstractPodcastDataProvider(Class<PodcastDataProvider> providerClass,            String providerAuthority) {        super(providerClass, providerAuthority);    }    public void setUp() throws Exception{        super.setUp();        //clear out all the old data.        PodcastDataProvider dataProvider =             (PodcastDataProvider)getMockContentResolver()            .acquireContentProviderClient(Feed.BASE_AUTH)            .getLocalContentProvider();        dataProvider.deleteAll();    }}

to setup a test data provider that will be backed by a different database than the actual application.

To test the DAO, create another class which extends AbstractPodcastDataProvider and use the

getMockContentResolver();

method to get an instance of a content resolver that will use the test database instead of the application database.