11

Additional Information:

To clarify, the app under test uses a ContentProvider to access the database.

Edit:

If anyone is willing and able to help me debug this. The full project is available here. In the issue107-contentprovider branch, BaseballCardListAddCardsTest.

Question:

When I run two of my Android JUnit tests separately, they pass just fine. However, when I run them together, the first one passes and the second one fails. The problem appears to be that the first test run adds a row to the underlying database. tearDown() correctly deletes the database, but the second test still starts with the dirty data displayed in the ListView although the database does not contain the extra row. (I confirmed this using adb shell.) Does anyone have any ideas how I can fix this problem?

The Activity class being tested can be found here.

Here is my test code:

/**
 * Tests for the {@link BaseballCardList} activity when the database contains
 * data.
 */
public class BaseballCardListWithDataTest extends
        ActivityInstrumentationTestCase2<BaseballCardList> {

    /**
     * Create instrumented test cases for {@link BaseballCardList}.
     */
    public BaseballCardListWithDataTest() {
        super(BaseballCardList.class);
    }

    /**
     * Set up test fixture. This consists of an instance of the
     * {@link BaseballCardList} activity, its {@link ListView}, and a populated
     * database.
     *
     * @throws Exception
     *             If an error occurs while chaining to the super class.
     */
    @Override
    public void setUp() throws Exception {
        super.setUp();

        this.inst = this.getInstrumentation();

        // Create the database and populate table with test data
        InputStream cardInputStream = this.inst.getContext().getAssets()
                .open(BBCTTestUtil.CARD_DATA);
        BaseballCardCsvFileReader cardInput = new BaseballCardCsvFileReader(
                cardInputStream, true);
        this.allCards = cardInput.getAllBaseballCards();
        cardInput.close();

        this.dbUtil = new DatabaseUtil(this.inst.getTargetContext());
        this.dbUtil.populateTable(this.allCards);

        // Start Activity
        this.activity = this.getActivity();
        this.listView = (ListView) this.activity
                .findViewById(android.R.id.list);
        this.newCard = new BaseballCard("Code Guru Apps", 1993, 1, 50000, 1,
                "Code Guru", "Code Guru Devs", "Catcher");
    }

    /**
     * Tear down the test fixture by calling {@link Activity#finish()} and
     * deleting the database.
     *
     * @throws Exception
     *             If an error occurs while chaining to the super class.
     */
    @Override
    public void tearDown() throws Exception {
        this.dbUtil.deleteDatabase();

        super.tearDown();
    }

    /**
     * Check preconditions which must hold to guarantee the validity of all
     * other tests. Assert that the {@link Activity} to test and its
     * {@link ListView} are not <code>null</code>, that the {@link ListView}
     * contains the expected data, and that the database was created with the
     * correct table and populated with the correct data.
     */
    public void testPreConditions() {
        Assert.assertNotNull(this.activity);

        BBCTTestUtil.assertDatabaseCreated(this.inst.getTargetContext());
        Assert.assertTrue(this.dbUtil.containsAllBaseballCards(this.allCards));

        Assert.assertNotNull(this.listView);
        BBCTTestUtil.assertListViewContainsItems(this.inst, this.allCards,
                this.listView);
    }

    /**
     * Test that the {@link ListView} is updated when the user adds a new card
     * which matches the current filter.
     *
     * @throws Throwable
     *             If an error occurs while the portion of the test on the UI
     *             thread runs.
     */
    public void testAddCardMatchingCurrentFilter() throws Throwable {
        this.testYearFilter();

        Activity cardDetails = BBCTTestUtil.testMenuItem(this.inst,
                this.activity, R.id.add_menu, BaseballCardDetails.class);
        BBCTTestUtil.addCard(this, cardDetails, this.newCard);
        BBCTTestUtil.clickCardDetailsDone(this, cardDetails);

        this.expectedCards.add(this.newCard);
        BBCTTestUtil.assertListViewContainsItems(this.inst, this.expectedCards,
                this.listView);
    }

    /**
     * Test that the {@link ListView} is updated when the user adds a new card
     * after an active filter was cleared.
     *
     * @throws Throwable
     *             If an error occurs while the portion of the test on the UI
     *             thread runs.
     */
    public void testAddCardAfterClearFilter() throws Throwable {
        this.testClearFilter();
        Activity cardDetails = BBCTTestUtil.testMenuItem(this.inst,
                this.activity, R.id.add_menu, BaseballCardDetails.class);
        BBCTTestUtil.addCard(this, cardDetails, this.newCard);
        BBCTTestUtil.clickCardDetailsDone(this, cardDetails);

        this.allCards.add(this.newCard);
        BBCTTestUtil.assertListViewContainsItems(this.inst, this.allCards,
                this.listView);
    }

    private List<BaseballCard> allCards;
    private List<BaseballCard> expectedCards;
    private Instrumentation inst = null;
    private Activity activity = null;
    private DatabaseUtil dbUtil = null;
    private ListView listView = null;
    private BaseballCard newCard = null;
    private static final int TIME_OUT = 5 * 1000; // 5 seconds
    private static final String TAG = BaseballCardListWithDataTest.class
            .getName();
}
Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268

1 Answers1

0

It appears that a ContentProvider's lifecycle is tied to that of an Application not of the Activity that acesses it. Also, from what I can tell, ActivityInstrumentationTestCase2 creates a single Application for all the tests; only the Activity is destroyed and restarted for each test. This means that the each test will share the same ContentProvider. This means that the database file is opened with the first access by the ContentProvider and closed only after all test methods in the ActivityInstrumentationTestCase2 have finished. Since the database file remains open between test cases, the data can be accessed even after the file is deleted from the underlying file system. My solution was to delete the rows of the database individually rather than deleting the entire database.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268