Testowanie ViewPager (i CursorLoader) za pomocą Robolectric

91

Czy ktoś wie, jak przetestować następującą konfigurację za pomocą Robolectric?

Fragment zawierający ViewPager, dane załadowane za pomocą CursorLoader.

Z poniższym kodem CursorLoader nigdy nie jest umieszczany w adapterze pagera widoku. Utknąłem na await()telefonie.

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
źródło
6
Wygląda na to, że próbujesz napisać więcej testów systemowych niż testów jednostkowych. Czy to prawidłowe założenie? Jeśli tak, to można by to zrobić dużo łatwiej z Robotium lub Espresso. Robolectric jest bardzo pomocny, gdy próbujesz napisać testy jednostkowe lub integracyjne, które mają odniesienia do systemu Android, ale nie testujesz tego, co zobaczy użytkownik.
Elliott,
Tęsknię za głosem, Robolectric próbuje pomóc Ci przetestować to, co widzi użytkownik. Problem, który myślę, że się napotkasz, polega na tym, że musisz napisać zbyt dużo kodu bootowania, który prawdopodobnie nie przetestuje poprawnej ścieżki kodu w środowisku produkcyjnym, a próba użycia czasowego oczekiwania generalnie sprawia, że ​​test jest kruchy, ponieważ nie wiadomo, jak tak długo, jak zajmie zadania synchronizacji.
Elliott,
Próbuję uzyskać szybkie testy. Mogę i często piszę testy w Robotium, ale działają one znacznie wolniej niż Robolectric. Idealnym światem dla mnie byłby brak konfiguracji, wsparcia i utrzymania 2 zestawów testów. Taki był mój zamiar tutaj.
Corey Scott,
1
Tylko myśl, Robolectric.runBackgroundTasks();coś dobrego - prawdopodobnie zamiastawait
weston
1
Zgadzam się z @Elliott, że próbujesz stworzyć testy integracyjne lub testy scenariuszy, takie jak testy jednostkowe, i nie do tego to służyło, jeśli chcesz włamać się i nadużywać robolectric, ale ma to swoją cenę. Robolectric udostępnia tylko kody pośredniczące, a nie klasy.
codeScriber

Odpowiedzi:

1

Nie jestem pewien, ale założyłbym się, że kod wewnętrzny próbuje użyć an AsyncTaskdo wywołania metody ładowania kursora loadInBackground(). Być może widzisz impas, ponieważ AsyncTaskpróby wywołania onPostExecute(). To wywołanie spróbuje uruchomić się w głównym wątku interfejsu użytkownika, który jest wątkiem procedury testowej. To nigdy nie może się zdarzyć, ponieważ utknąłeś w oczekiwaniu ze swoją procedurą testową na stosie wywołań.

Spróbuj przenieść swoją makietę na wyższy poziom, tak aby w tle testu nic się tak naprawdę nie działo. google `AsyncTask testów jednostkowych android impasu” , aby zobaczyć przykłady, gdzie inni wpadł podobnych problemów.

bigh_29
źródło