4

I have been struggling with a unit testing problem on Android for a while now. My app uses a Sqlite DB to store details on vehicles. I have recently added the ContentProvider pattern to retrieve the data (the database used to be accessed directly).

The app works fine, but my tests sporadically fail when run in 'run' mode on eclipse - they pass in 'debug' mode. In my setUp() method, I create a RenamingDelegatingContext in order to create a test version of my database. All this does is prefix "test." to the name of my database to ensure the 'real' database is not touched by test code. I then pass this to a 'data provider' class to keep as a instance variable until the first call to getWriteableDatabase() is called:

public class VehicleProviderTest extends InstrumentationTestCase
{

RenamingDelegatingContext renamingDelegatingContext;
@Override
protected void setUp() throws Exception
{
    super.setUp();
    if(null == renamingDelegatingContext)
    {
        renamingDelegatingContext = new RenamingDelegatingContext(getInstrumentation().getTargetContext(), "test.");

    }

    Log.d("UKMPG", "Initialising UKMPGDataProvider with test context: " + renamingDelegatingContext.getClass().toString());
    MPGDataProvider.init(getTestContext(), Constants.DATABASE_NAME);
    deleteTestDatabase();
}
}

My ContentProvider's onCreate() method does a similar job in that it also passes a Context to the data provider class:

    @Override
public boolean onCreate()
{
    Context context = getContext();
    Log.d("UKMPG", "ContentProvider.onCreate called. Context: " + context.getClass().toString());
    MPGDataProvider.init(getContext(), Constants.DATABASE_NAME);
    return true;
}

Now, the problem; when I run my tests (again, this doesn't happen in debug mode), onCreate() in my ContentProvider is called after my setUp() method has passed the RenamingDelegatingContext to the data provider class, resulting in it being overwritten. This means the live database will be used for the tests (they will fail as the tests expect an empty database).

Here is some logcat showing the overwriting taking place with notes explaining what is happening:

ContentProvider.onCreate called and passes Context to UKMPGDataProvider:

01-11 19:22:11.404: D/UKMPG(480): ContentProvider.onCreate called. Context: class android.app.Application
01-11 19:22:11.414: D/UKMPG(480): UKMPGDataProvider.init called. Context: class android.app.Application, db name :mpg_tracker.db

Tests pass a RenamingDelegatingContext to UKMPGDataProvider:

01-11 19:22:13.234: D/UKMPG(498): Initialising UKMPGDataProvider with test context: class android.test.RenamingDelegatingContext
01-11 19:22:13.234: D/UKMPG(498): UKMPGDataProvider.init called. Context: class android.test.RenamingDelegatingContext, db name :mpg_tracker.db

ContentProvider.onCreate called again (different PID) and passes ApplicationContext to UKMPGDataProvider, overwriting the RenamingDelegatingContext:

01-11 19:22:13.254: D/UKMPG(498): ContentProvider.onCreate called. Context: class android.app.ApplicationContext
01-11 19:22:13.254: D/UKMPG(498): UKMPGDataProvider.init called. Context: class android.app.ApplicationContext, db name :mpg_tracker.db

First call to getWriteableDatabase() has happened, so database is going to be created with wrong Context:

01-11 19:22:13.265: D/UKMPG(498): database null - going to create it

Yet another call to ContentProvider.onCreate has happened! This time with a regular Context. The damage is already done however, so this doesn't really make any difference:

01-11 19:22:13.265: D/UKMPG(498): ContentProvider.onCreate called. Context: class android.app.Application

Creating DB with wrong Context:

01-11 19:22:13.265: D/UKMPG(498): Creating DB instance with Context: class android.app.ApplicationContext

init() method called corresponding to the last ContentProvider.onCreate() call:

01-11 19:22:13.274: D/UKMPG(498): UKMPGDataProvider.init called. Context: class android.app.Application, db name :mpg_tracker.db

getWriteableDatabase() is returning a non-test database:

01-11 19:22:13.374: D/UKMPG(498): getWritableDatabase. Path of returned db: /data/data/barry.contentproviderexample/databases/mpg_tracker.db

Failed to delete database for next test as the 'test' database does not exist:

01-11 19:22:13.615: D/UKMPG(498): Database deleted:false

For completeness, here is the logcat from the same test run when run in debug mode:

01-11 19:37:09.514: D/UKMPG(598): ContentProvider.onCreate called. Context: class android.app.Application
01-11 19:37:09.514: D/UKMPG(598): UKMPGDataProvider.init called. Context: class android.app.Application, db name :mpg_tracker.db
01-11 19:37:11.313: D/UKMPG(616): ContentProvider.onCreate called. Context: class android.app.Application
01-11 19:37:11.313: D/UKMPG(616): UKMPGDataProvider.init called. Context: class android.app.Application, db name :mpg_tracker.db

At this point ContentProder.onCreate has been called twice with a non-test Context, but this happens before my setUp() method, so it doesn't affect the tests:

01-11 19:37:14.173: D/UKMPG(616): Initialising UKMPGDataProvider with test context: class android.test.RenamingDelegatingContext
01-11 19:37:14.173: D/UKMPG(616): UKMPGDataProvider.init called. Context: class android.test.RenamingDelegatingContext, db name :mpg_tracker.db
01-11 19:37:14.213: D/UKMPG(616): database null - going to create it
01-11 19:37:14.213: D/UKMPG(616): Creating DB instance with Context: class android.test.RenamingDelegatingContext
01-11 19:37:14.364: D/UKMPG(616): getWritableDatabase. Path of returned db: /data/data/barry.contentproviderexample/databases/test.mpg_tracker.db
01-11 19:37:14.794: D/UKMPG(616): Database deleted:true

Can anyone help me figure this out? Am I doing something wrong, or is this a (threading?) bug in Android?

It is repeatable on devices and across Android versions 1.6 to 4.0.

richsage
  • 26,912
  • 8
  • 58
  • 65
barry
  • 4,037
  • 6
  • 41
  • 68

1 Answers1

2

I think you should call setContext() in your setUp():

// warning: untested code
protected void setUp() throws Exception
{
    super.setUp();
    setContext(new RenamingDelegatingContext(getTargetContext(), "test.");

    ...
}

In that case you may need to change your base class to ProviderTestCase2 probably.

Diego Torres Milano
  • 65,697
  • 9
  • 111
  • 134
  • Thanks for the suggestion, I'll give it a go. Presumably this will set the Context for the ContentProvider so I won't need to worry about my Renamingdelegatingcontext being overwritten? – barry Jan 11 '12 at 21:56
  • Well currently I pass A Renamingdelegatingcontext from my setUp method to a data provider class. This is overwritten by the ContentProvider. Does your suggestion mean I won't need to pass the Renamingdelegatingcontext at all, as the setContext will take care of it? – barry Jan 12 '12 at 15:47
  • 1
    Thanks - this appears to be working. Any tips on what to do for test classes which need to extend ActivityInstrumentationTestCase2? These will still need to use a test database. – barry Jan 12 '12 at 20:03
  • There are some examples that may help you in [Android Applications Testing Guide](http://www.amazon.com/Android-Application-Testing-Torres-Milano/dp/1849513503) – Diego Torres Milano Jan 12 '12 at 20:59