Menguji ViewPager (dan CursorLoader) dengan Robolectric

91

Adakah yang tahu cara menguji penyiapan berikut menggunakan Robolectric?

Fragmen berisi ViewPager, data dimuat dengan CursorLoader.

Dengan kode di bawah ini, CursorLoader tidak pernah didorong ke adaptor untuk halaman tampilan. Saya terjebak di await()telepon.

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
sumber
6
Sepertinya Anda mencoba menulis lebih banyak Pengujian Sistem daripada Pengujian Unit, apakah itu asumsi yang benar? Jika demikian, ini bisa dilakukan lebih mudah dengan Robotium atau Espresso. Robolectric sangat membantu saat Anda mencoba menulis pengujian unit atau integrasi yang memiliki referensi khusus Android tetapi tidak menguji apa yang akan dilihat pengguna.
Elliott
Saya rindu berbicara sedikit, Robolectric mencoba membantu Anda menguji apa yang dilihat pengguna. Masalah yang menurut saya akan Anda hadapi adalah bahwa Anda harus menulis terlalu banyak kode boot strapping yang tidak mungkin menguji jalur kode yang benar dalam produksi dan mencoba menggunakan waktu tunggu biasanya membuat pengujian rapuh karena tidak ada yang tahu caranya selama tugas asinkron akan berlangsung.
Elliott
Saya mencoba untuk mendapatkan tes cepat. Saya dapat dan sering melakukan tes tulis di Robotium tetapi mereka secara signifikan lebih lambat untuk dijalankan daripada Robolectric. Dunia yang ideal bagi saya adalah tidak memiliki pengaturan, dukungan, dan pemeliharaan 2 set pengujian. Inilah maksud saya di sini.
Corey Scott
1
Hanya berpikir, Robolectric.runBackgroundTasks();baik apapun - mungkin bukanawait
weston
1
saya setuju dengan @Elliott Anda mencoba untuk membuat tes integrasi atau tes skenario seperti tes unit dan itu bukan apa maksudnya, jika Anda ingin meretas \ penyalahgunaan robolectric fine, tetapi ada harga. robolectric hanya menyediakan stub, bukan kelas.
codeScriber

Jawaban:

1

Saya tidak yakin, tapi saya berani bertaruh bahwa kode internal mencoba menggunakan AsyncTaskuntuk memanggil metode cursor loader loadInBackground(). Anda mungkin melihat kebuntuan karena AsyncTaskupaya untuk memanggil onPostExecute(). Panggilan tersebut akan mencoba dijalankan di thread UI utama Anda, yang kebetulan merupakan thread rutin pengujian Anda. Itu tidak akan pernah terjadi karena Anda terjebak dalam menunggu dengan rutinitas pengujian Anda di tumpukan panggilan.

Coba pindahkan mocking Anda ke level yang lebih tinggi sehingga tidak ada yang benar-benar terjadi di latar belakang dari pengujian. google ʻAsyncTask unit test android deadlock ' untuk melihat contoh di mana orang lain mengalami masalah serupa.

bigh_29
sumber