2013-07-05 20 views
7

Dato sto sviluppando un semplice ListFragment (in questo caso, si legge un elenco di artisti della MediaStore, ma sarà anche leggere i dati da una fonte diversa in seguito) come questo:Test di un CursorLoader con 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); 
    } 
} 

voglio usare Robolectric + Mockito + awaitility alla prova il frammento si comporta correttamente su varie condizioni (ad esempio elenco vuoto o dati non validi, ecc). La mia classe di test si presenta così:

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

Poi durante l'esecuzione di questo test in IntelliJ o Maven il test avrà esito negativo, l'adattatore restituisce sempre un conteggio pari a zero.

L'istruzione System.out.println in onCreateLoader restituisce tuttavia . Devo prestare particolare attenzione a Mockito nei thread di sfondo ? (il metodo loadInBackground viene eseguito su un thread di lavoro).

risposta

1

La soluzione è quella di utilizzare:

Robolectric.flushForegroundThreadScheduler(); 

(Robolectric 3.0)

Questo farà eseguire immediatamente tutte le attività, tra cui il CursorLoader.

+0

Potete darmi una lezione di test unitario completa? Sto anche affrontando lo stesso problema e usando lo stesso loaderloader. – user2566484

1

Ho appena fatto test Loader lavorando nel mio codice. Nel mio caso ho trovato più diretto testare il caricatore stesso piuttosto che cercare di instradarlo attraverso il codice Fragment.

ho liquidata con una versione leggermente modificata del codice da questo post: https://groups.google.com/forum/#!msg/robolectric/xY5MF399jA8/V5PnUfh1D-wJ

In primo luogo, ho dovuto implementare alcune classi ombra perché Robolectric2 non include il codice ombra per le classi AsyncTaskLoader o Loader. Se non hai mai aggiunto una classe shadow, sappi che è importante metterli nel pacchetto corretto. Entrambe queste ombre dovrebbero vivere in 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(); 
    } 
} 
} 

Poi, a seconda della configurazione è possibile aggiungere un'annotazione per utilizzare le classi personalizzate

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

A questo punto chiamare loader.onStartLoading() ha causato l'esecuzione del caricatore come previsto senza dover hackerare alcuna attesa mandi nei miei casi di test.

Spero che questo aiuti. Non ho provato a utilizzare LoaderManager con questo metodo di test, quindi non posso verificare che funzioni attraverso quella chiamata.

Nota: il motivo per cui ho aggiunto ShadowLoader è perché stavo cercando isReset() restituiva true quando non me l'aspettavo.

+0

Purtroppo non funziona per me. – rumpel

Problemi correlati