Test de ViewPager (et CursorLoader) avec Robolectric

91

Quelqu'un sait-il comment tester la configuration suivante à l'aide de Robolectric?

Fragment contenant un ViewPager, des données chargées avec un CursorLoader.

Avec le code ci-dessous, le CursorLoader n'est jamais poussé dans l'adaptateur pour le pager de vue. Je suis coincé à l' await()appel.

EventsFragmentTest.java:

@RunWith(CustomRobolectricTestRunner.class)
public class EventsFragmentTest extends AbstractDbAndUiDriver
{
    // which element in the view pager we are testing
    private static final int           TEST_INDEX = 0;

    protected SherlockFragmentActivity mActivity;
    protected EventsFragment_          mFragment;

    @Override
    @Before
    public void setUp() throws Exception
    {
        // create activity to hold the fragment
        this.mActivity = CustomRobolectricTestRunner.getActivity();

        // create and start the fragment
        this.mFragment = new EventsFragment_();
    }

    @Test
    public void sanityTest()
    {
        // create an event
        final Event event = this.createEvent();

        // create mock cursor loader
        final Cursor cursor = this.createMockEventCursor(event);
        this.mFragment.setCursorLoader(mock(CursorLoader.class));
        when(this.mFragment.getCursorLoader().loadInBackground()).thenReturn(cursor);
        CustomRobolectricTestRunner.startFragment(this.mActivity, this.mFragment);

        await().atMost(5, SECONDS).until(this.isCursorLoaderLoaded(), equalTo(true));

        // check for data displayed
        final TextView title = this.getTextView(R.id.event_view_title);
        final TextView text = this.getTextView(R.id.event_view_text);

        // exists and visible is enough for now
        this.getImageView(R.id.event_view_image);

        assertThat(title.getText().toString(), equalTo(event.getTitle()));
        assertThat(text.getText().toString(), is(event.getText()));

        // clean up
        cursor.close();
    }

    @Override
    protected View getRootView()
    {
        return ((ViewPager) this.mFragment.getView().findViewById(R.id.events_pager)).getChildAt(TEST_INDEX);
    }

    private Callable<Boolean> isCursorLoaderLoaded()
    {
        return new Callable<Boolean>()
        {
            public Boolean call() throws Exception
            {
                return EventsFragmentTest.this.mFragment.isCursorLoaderLoaded(); // The condition that must be fulfilled
            }
        };
    }

    /**
     * Create an event
     * 
     * @return
     */
    protected Event createEvent()
    {
        // create a random event
        final Event event = new Event();
        event.setImage(null);
        event.setLink("/some/link/" + RandomUtils.getRandomString(5)); //$NON-NLS-1$
        event.setResourceUri("/rest/uri/" + RandomUtils.getRandomDouble()); //$NON-NLS-1$
        event.setText("this is a test object " + RandomUtils.getRandomString(5)); //$NON-NLS-1$
        return event;
    }

    protected Cursor createMockEventCursor(final Event event)
    {
        // Create a mock cursor.
        final Cursor cursor = new CursorWrapper(mock(MockCursor.class));

        when(cursor.getCount()).thenReturn(1);
        when(cursor.getString(cursor.getColumnIndexOrThrow(EventTable.COLUMN_TEXT))).thenReturn(event.getText());
        when(cursor.getString(cursor.getColumnIndexOrThrow(EventTable.COLUMN_TITLE))).thenReturn(event.getTitle());
        when(cursor.getString(cursor.getColumnIndexOrThrow(EventTable.COLUMN_IMAGE))).thenReturn(event.getImage());
        when(cursor.getString(cursor.getColumnIndexOrThrow(EventTable.COLUMN_LINK))).thenReturn(event.getLink());
        when(cursor.getString(cursor.getColumnIndexOrThrow(EventTable.COLUMN_RESOURCE_URI))).thenReturn(
                        event.getResourceUri());

        // return created event
        return cursor;
    }

}

EventsFragment.java

@EFragment(resName = "events_fragment")
public class EventsFragment extends SherlockFragment implements LoaderCallbacks<Cursor>
{
    @ViewById(R.id.events_pager)
    protected ViewPager             mPager;

    @ViewById(R.id.events_indicator)
    protected CirclePageIndicator   mIndicator;

    @Pref
    protected ISharedPrefs_         mPreferences;

    protected EventsFragmentAdapter pageAdapter;

    private CursorLoader            mCursorLoader;

    /**
     * initialise the cursoradapter and the cursor loader manager.
     */
    @AfterViews
    void init()
    {
        final SherlockFragmentActivity activity = this.getSherlockActivity();

        this.pageAdapter = new EventsFragmentAdapter(activity.getSupportFragmentManager(), null);
        this.mPager.setAdapter(this.pageAdapter);
        this.mIndicator.setViewPager(this.mPager);
        this.getLoaderManager().initLoader(this.mPager.getId(), null, this);
    }

    /* (non-Javadoc)
     * @see android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader(int, android.os.Bundle)
     */
    @Override
    public Loader<Cursor> onCreateLoader(final int arg0, final Bundle arg1)
    {
        if (this.mCursorLoader == null)
        {
            // set sort to newest first
            final String sortOrder = BaseColumns._ID + " DESC"; //$NON-NLS-1$

            this.mCursorLoader = new CursorLoader(this.getActivity(), EventContentProvider.CONTENT_URI,
                            EventTable.getProjection(), AbstractDbTable.getWhereCondition(null),
                            AbstractDbTable.getWhereArgs(this.mPreferences, null), sortOrder);
        }
        return this.mCursorLoader;
    }

    /* (non-Javadoc)
     * @see android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished(android.support.v4.content.Loader, java.lang.Object)
     */
    @Override
    public void onLoadFinished(final Loader<Cursor> arg0, final Cursor cursor)
    {
        this.pageAdapter.swapCursor(cursor);

    }

    /* (non-Javadoc)
     * @see android.support.v4.app.LoaderManager.LoaderCallbacks#onLoaderReset(android.support.v4.content.Loader)
     */
    @Override
    public void onLoaderReset(final Loader<Cursor> arg0)
    {
        this.pageAdapter.swapCursor(null);
    }

    /**
     * Required for testing only.
     * 
     * @param cursorLoader
     */
    public void setCursorLoader(final CursorLoader cursorLoader)
    {
        this.mCursorLoader = cursorLoader;
    }

    /**
     * Required for testing only.
     * 
     * @param cursorLoader
     */
    public CursorLoader getCursorLoader()
    {
        return this.mCursorLoader;
    }

    public boolean isCursorLoaderLoaded()
    {
        return (this.pageAdapter.getCursor() != null);
    }
}
Corey Scott
la source
6
Il semble que vous essayez d'écrire davantage un test système plutôt qu'un test unitaire, est-ce une hypothèse correcte? Si tel est le cas, cela pourrait être fait beaucoup plus facilement avec Robotium ou Espresso. Robolectric est vraiment utile lorsque vous essayez d'écrire des tests unitaires ou d'intégration qui ont des références spécifiques à Android mais ne testez pas ce que l'utilisateur verra.
Elliott
Je manque un peu parlé, Robolectric essaie de vous aider à tester ce que voit l'utilisateur. Le problème que je pense que vous allez rencontrer est que vous devez écrire trop de code de démarrage qui ne testera pas le chemin de code correct en production et que d'essayer d'utiliser des temps d'attente chronométrés rend généralement le test fragile car on ne sait pas comment longues tâches asynchrones prendront.
Elliott
J'essaye d'obtenir des tests rapides. Je peux et j'écris souvent des tests dans Robotium, mais ils sont beaucoup plus lents à exécuter que Robolectric. Un monde idéal pour moi serait de ne pas avoir configuré, pris en charge et entretenu 2 séries de tests. C'était mon intention ici.
Corey Scott
1
Juste une pensée, Robolectric.runBackgroundTasks();tout bon - probablement au lieu duawait
weston
1
Je suis d'accord avec @Elliott que vous essayez de créer des tests d'intégration ou des tests de scénario comme des tests unitaires et ce n'est pas ce que cela signifie, si vous voulez pirater \ abuse d'une amende de robolectric, mais il y a un prix. robolectric fournit uniquement des stubs, pas des classes.
codeScriber

Réponses:

1

Je ne suis pas certain, mais je parierais que le code interne essaie d'utiliser un AsyncTaskpour invoquer la loadInBackground()méthode du chargeur de curseur . Vous constatez peut-être une impasse parce que le AsyncTasktente d'appeler onPostExecute(). Cet appel essaiera de s'exécuter dans votre thread d'interface utilisateur principal, qui se trouve être le thread de votre routine de test. Cela ne peut jamais arriver car vous êtes bloqué dans une attente avec votre routine de test sur la pile d'appels.

Essayez de faire passer votre maquette à un niveau supérieur afin que rien ne se passe vraiment en arrière-plan du test. google `AsyncTask unit test android deadlock ' pour voir des exemples où d'autres personnes ont rencontré des problèmes similaires.

bigh_29
la source