Testing ViewPager (and CursorLoader) with Robolectric

Does anyone know how to test the following setup using Robolectric?

Fragment containing a ViewPager, data loaded with a CursorLoader.

  • How to set the height of an item row in GridLayoutManager
  • Android: Overlay on Android Camera Preview
  • Android getting exact scroll position in ListView
  • The constructor notification is deprecated
  • First frame of the background video doesn't show on Android
  • Android Edit Text Covered By Keyboard on Second Tap
  • With the code below, the CursorLoader is never pushed into the adapter for the view pager.
    I get stuck at the await() call.

    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);
        }
    }
    

    Related posts:

    Android Fatal signal 11 (SIGSEGV) at 0x00000040 (code=1) Error
    Crashlytics not finding API Key in crashlytics.properties at runtime
    Android: resume app from previous position
    Android: Is it possible to use string/enum in drawable selector?
    Login to website using android account
    toUpperCase on Android is incorrect for two-argument and default Greek and Turkish Locales
  • Why the current year in the date saved as 3912?
  • Bluetooth LE ScanFilters don't work on Android M
  • Eclipse creates fragment layout automatically, how can i disable it
  • Include a few pages in one page in Jquery mobile
  • Android Asynctask vs Runnable vs timertask vs Service
  • Android Cast v3: can't find any device
  • One Solution collect form web for “Testing ViewPager (and CursorLoader) with Robolectric”

    I’m not certain, but I would wager that the internal code is trying to use an AsyncTask to invoke the cursor loader’s loadInBackground() method. You might be seeing a deadlock because the AsyncTask tries to invoke onPostExecute(). That call will try to run in your main UI thread, which happens to be the thread of your test routine. That can never happen because you are stuck in a wait with your test routine on the call stack.

    Try moving your mocking up to a higher level so that nothing really happens in the background from the test. google `AsyncTask unit test android deadlock’ to see examples where other people ran into similar problems.

    Android Babe is a Google Android Fan, All about Android Phones, Android Wear, Android Dev and Android Games Apps and so on.