Testen von ViewPager (und CursorLoader) mit Robolectric

91

Weiß jemand, wie man das folgende Setup mit Robolectric testet?

Fragment mit einem ViewPager, Daten, die mit einem CursorLoader geladen wurden.

Mit dem folgenden Code wird der CursorLoader niemals in den Adapter für den Ansichtspager verschoben. Ich stecke beim await()Anruf fest.

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
quelle
6
Es sieht so aus, als würden Sie eher einen Systemtest als einen Unit-Test schreiben. Ist das eine korrekte Annahme? Wenn ja, könnte dies mit Robotium oder Espresso viel einfacher gemacht werden. Robolectric ist sehr hilfreich, wenn Sie versuchen, Unit- oder Integrationstests zu schreiben, die Android-spezifische Referenzen haben, aber nicht testen, was der Benutzer sehen wird.
Elliott
Ich vermisse etwas gesprochen, Robolectric versucht Ihnen zu helfen, zu testen, was der Benutzer sieht. Das Problem, auf das Sie wahrscheinlich stoßen werden, ist, dass Sie zu viel Boot-Strapping-Code schreiben müssen, der möglicherweise nicht den richtigen Codepfad in der Produktion testet, und der Versuch, zeitgesteuerte Wartezeiten zu verwenden, macht den Test im Allgemeinen brüchig, da nicht abzusehen ist, wie Es dauert lange asynchrone Aufgaben.
Elliott
Ich versuche schnelle Tests zu bekommen. Ich kann und mache oft Tests in Robotium, aber sie laufen deutlich langsamer als Robolectric. Eine ideale Welt für mich wäre es, nicht zwei Testsätze einzurichten, zu unterstützen und zu warten. Das war meine Absicht hier.
Corey Scott
1
Nur ein Gedanke, Robolectric.runBackgroundTasks();irgendetwas Gutes - wahrscheinlich anstelle desawait
Weston
1
Ich stimme @Elliott zu, dass Sie versuchen, Integrationstests oder Szenariotests wie Unit-Tests zu erstellen, und es ist nicht das, wofür es bedeutet, wenn Sie robolectric fein hacken \ missbrauchen wollen, aber es gibt einen Preis. robolectric bietet nur Stubs, keine Klassen.
CodeScriber

Antworten:

1

Ich bin nicht sicher, aber ich würde wetten, dass der interne Code versucht, AsyncTaskdie loadInBackground()Methode des Cursorladers mit a aufzurufen . Möglicherweise sehen Sie einen Deadlock, weil AsyncTaskversucht wird, ihn aufzurufen onPostExecute(). Dieser Aufruf versucht, in Ihrem Haupt-UI-Thread ausgeführt zu werden, der zufällig der Thread Ihrer Testroutine ist. Dies kann niemals passieren, da Sie mit Ihrer Testroutine auf dem Aufrufstapel warten müssen.

Versuchen Sie, Ihre Verspottung auf eine höhere Ebene zu bringen, damit im Hintergrund des Tests nichts wirklich passiert. google `AsyncTask Unit Test Android Deadlock ' , um Beispiele zu sehen, bei denen andere Leute auf ähnliche Probleme stießen .

bigh_29
quelle