2013-07-05 8 views
7

Учитывая я разрабатываю простой ListFragment (в данном случае, он читает список художников из MediaStore, но и считывать данные из другого источника позже), как это:Тестирование CursorLoader с Robolectric & Mockito

@EFragment 
public class ArtistsFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> { 
    private static final String TAG = ArtistsFragment.class.getName(); 
    private SimpleCursorAdapter mAdapter; 

    Uri uri = MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI; 

    CursorLoader mCursorLoader; 

    @AfterViews 
    void setupView() { 
     mAdapter = new SimpleCursorAdapter(getActivity(), 
       android.R.layout.simple_list_item_1, null, 
       new String[]{MediaStore.Audio.Artists.ARTIST}, // lists path of files 
       new int[]{android.R.id.text1}, 0); 

     setListAdapter(mAdapter); 

     getLoaderManager().initLoader(0, null, this); 
    } 

    @Override 
    public Loader<Cursor> onCreateLoader(int id, Bundle args) { 
     if (mCursorLoader == null) { 
      mCursorLoader = new CursorLoader(getActivity(), uri, new String[]{MediaStore.Audio.Artists._ID, MediaStore.Audio.Artists.ARTIST}, 
        null, null, MediaStore.Audio.Artists.ARTIST + " ASC"); 
     } else { 
      System.out.println("mCursorLoader.count: " + mCursorLoader.loadInBackground().getCount());    
     } 
     return mCursorLoader; 
    } 

    @Override 
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 
     setListShown(true); 
     mAdapter.swapCursor(data); 
    } 

    @Override 
    public void onLoaderReset(Loader<Cursor> loader) { 
     mAdapter.swapCursor(null); 
    } 
} 

Я хочу использовать Robolectric + Mockito + awaitility доказыванию фрагмента ведет себя должным образом на различных условиях (например, пустой список или недостоверных данных и т.д.). Мой тестовый класс выглядит следующим образом:

@RunWith(RobolectricTestRunner.class) 
public class ArtistsFragmentTest { 
    @Test 
    public void shouldNotBeNull() { 
     final ArtistsFragment myFragment = ArtistsFragment_.builder().build(); 
     assertNotNull(myFragment); 

     // Create a mock cursor. 
     final Cursor mc = getSampleArtistCursor(); 
     when(mc.getCount()).thenReturn(1); 
     when(mc.getInt(0)).thenReturn(1); 
     when(mc.getString(1)).thenReturn("Sample Title"); 

     myFragment.mCursorLoader = mock(CursorLoader.class); 
     when(myFragment.mCursorLoader.loadInBackground()).thenReturn(mc); 

     startFragment(myFragment); 

     assertNotNull(myFragment.getListView()); 
     await().atMost(5, TimeUnit.SECONDS).until(new Callable<Integer>() { 
      @Override 
      public Integer call() throws Exception { 
       return myFragment.getListAdapter().getCount(); 
      } 
     }, equalTo(1)); 

     System.out.println(myFragment.getListAdapter().getCount()); 
    } 

    private Cursor getSampleArtistCursor() { 
     return new CursorWrapper(mock(MockCursor.class)); 
    } 
} 

Тогда при выполнении этого теста в IntelliJ или Maven тест проваливается, адаптер всегда будет возвращать счетчик в ноль.

Оператор System.out.println в onCreateLoader, однако, возвращает . Нужно ли ухаживать за Mockito в фоновых потоках? (метод loadInBackground работает на рабочем потоке).

ответ

1

Решение состоит в том, чтобы использовать:

Robolectric.flushForegroundThreadScheduler(); 

(Robolectric 3.0)

Это будет выполнять все задачи немедленно, в том числе CursorLoader.

+0

Можете ли вы дать мне полный комплект тестовых тестов Unit? Я также сталкиваюсь с той же проблемой и с использованием того же самого загрузчика. – user2566484

1

Я только что получил тесты Loader, работающие в моем коде. В моем случае я нашел более непосредственным испытание самого загрузчика, а не попытку маршрутизации его через код фрагмента.

я завелся, используя слегка модифицированную версию кода с этого поста: https://groups.google.com/forum/#!msg/robolectric/xY5MF399jA8/V5PnUfh1D-wJ

Во-первых, я должен был реализовать некоторые теневые классы, потому что Robolectric2 не включает теневой код для AsyncTaskLoader или Loader классов. Если вы никогда не добавляли теневой класс, знаете, что важно разместить их в правильном пакете. Обе эти тени должны жить в android.support.v4.content.

ShadowLoader

@Implements(Loader.class) 
public class ShadowLoader<D> { 

// ////////////////////// 
// Constants 

// ////////////////////// 
// Fields 
protected boolean reset; 

// ////////////////////// 
// Constructors 

// ////////////////////// 
// Getter & Setter 

// ////////////////////// 
// Methods from SuperClass/Interfaces 

@Implementation 
public void reset() { 
    reset = true; 
} 

@Implementation 
public boolean isReset() { 
    return reset; 
} 
// ////////////////////// 
// Methods 

// ////////////////////// 
// Inner and Anonymous Classes 
} 

ShadowAsyncTaskLoader

@Implements(AsyncTaskLoader.class) 
public class ShadowAsyncTaskLoader<D> extends ShadowLoader { 

@RealObject 
private AsyncTaskLoader<D> asyncTaskLoader; 

@Implementation 
void executePendingTask() { 
    new AsyncTask<Void, Void, D>() { 
     @Override 
     protected D doInBackground(Void... params) { 
      return (D) asyncTaskLoader.loadInBackground(); 
     } 

     @Override 
     protected void onPostExecute(D data) { 
      updateLastLoadCompleteTimeField(); 
      asyncTaskLoader.deliverResult(data); 
     } 

     @Override 
     protected void onCancelled(D data) { 
      updateLastLoadCompleteTimeField(); 
      asyncTaskLoader.onCanceled(data); 

      executePendingTask(); 
     } 
    }.execute((Void)null); 
} 


public void setReset(boolean reset) { 
    this.reset = reset; 
} 

private void updateLastLoadCompleteTimeField() { 
    try { 
     Field mLastLoadCompleteTimeField = AsyncTaskLoader.class.getDeclaredField("mLastLoadCompleteTime"); 
     if(!mLastLoadCompleteTimeField.isAccessible()) { 
      mLastLoadCompleteTimeField.setAccessible(true); 
     } 
     mLastLoadCompleteTimeField.set(asyncTaskLoader, SystemClock.uptimeMillis()); 

    } catch(NoSuchFieldException e) { 
     e.printStackTrace(); 
    } catch(IllegalAccessException e) { 
     e.printStackTrace(); 
    } 
} 
} 

Затем, в зависимости от конфигурации вы можете добавить аннотацию использовать пользовательские классы

@Config(shadows = { ShadowAsyncTaskLoader.class, ShadowLoader.class}) 

На данный момент вызывающему loader.onStartLoading() заставил загрузчик работать как ожидалось, без необходимости взломать ожидающий компьютер в мои тестовые примеры.

Надеюсь, это поможет. Я не пробовал использовать LoaderManager с помощью этого метода тестирования, поэтому я не могу проверить, работает ли он через этот вызов.

Примечание: причина, по которой я добавил ShadowLoader, заключается в том, что я обнаружил, что isReset() возвращал true, когда я этого не ожидал.

+0

Не работает для меня, к сожалению. – rumpel

Смежные вопросы