2014-04-20 8 views
5

Sono un neofita del pugnale e recentemente ho iniziato a usare il pugnale in uno dei miei progetti, perché il concetto di gestire l'iniezione delle dipendenze in modo diverso per i test e produzione, quindi essere in grado di iniettare oggetti finti che potrei usare per i test è stato grandioso.Come progettare un'applicazione Android usando il pugnale con Testing in mente

Ho modificato la mia domanda per seguire lo stile definito nello dagger simple-android example.

Dopo aver impostato tutto, ho riscontrato problemi di iniezione e non ho potuto sovraccaricare completamente le iniezioni dall'applicazione di produzione con la logica di test.

Sto cercando consigli su come impostare questo in modo che i miei test possano effettivamente iniettare in modo differenziale con mock o altri oggetti per testare se necessario, e non essere troppo sfacciati. Attualmente, il MainActivityTest viene iniettata in modo corretto, ma quando arriviamo al MainActivity, si va al PhoneApplication e inietta usando il suo oggetto grafico

Ho incluso quello che ho qui di seguito. Qualsiasi aiuto sarebbe molto apprezzato!


Ecco il mio PhoneApplication, sulla base del DemoApplication.

public class PhoneApplication extends Application { 
    private ObjectGraph graph; 

    @Override 
    public void onCreate() { 
     super.onCreate(); 

     graph = ObjectGraph.create(getModules().toArray()); 
    } 

    protected List<Object> getModules() { 
     return Arrays.asList(new AndroidModule(this), new PhoneModule()); 
    } 

    public void inject(Object object) { 
     graph.inject(object); 
    } 
} 

Ed ecco la mia AndroidModule

@Module(library = true, injects = MainActivity.class) 
public class AndroidModule { 
    private final Context context; 

    public AndroidModule(Context context) { 
     this.context = context; 
    } 

    /** 
    * Allow the application context to be injected but require that it be 
    * annotated with {@link ForApplication @Annotation} to explicitly 
    * differentiate it from an activity context. 
    */ 
    @Provides 
    @Singleton 
    @ForApplication 
    Context provideApplicationContext() { 
     return context; 
    } 

    @Provides 
    @Singleton 
    NotificationManager provideNotificationManager() { 
     return (NotificationManager) context 
       .getSystemService(Application.NOTIFICATION_SERVICE); 
    } 

    @Provides 
    @Singleton 
    LocalBroadcastManager provideLocalBroadcastManager() { 
     return LocalBroadcastManager.getInstance(context); 
    } 

    @Provides 
    @Singleton 
    ContentResolver provideContentResolver() { 
     return context.getContentResolver(); 
    } 

} 

Sulla base della esempio, ho anche istituito le mie attività per usare una base di attività.

public abstract class ActionBarBaseActivity extends ActionBarActivity { 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 

     ((PhoneApplication) getApplication()).inject(this); 
    } 
} 

Poi nel mio MainActivity Ho il seguente

public class MainActivity extends ActionBarBaseActivity { 

... 

    @Inject 
    LocalBroadcastManager localBroadcastManager; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
... 
     try { 
      messageReceivedIntentFilter = new IntentFilter(
        Constants.EVENT_MESSAGE_RECEIVED, 
        "vnd.android.cursor.dir/vnd." 
          + DataProviderContract.AUTHORITY + "." 
          + DataProviderContract.MESSAGES_TABLE_NAME); 

      localBroadcastManager.registerReceiver(messageReceiver, 
        messageReceivedIntentFilter); 
     } catch (MalformedMimeTypeException e) { 
      Log.e(LOG_TAG, 
        "An error occurred registering an Intent for EVENT_MESSAGE_RECEIVED", 
        e); 
     } 
... 
    } 
... 
} 

Questo ha funzionato grande e le iniezioni di scivolare in posizione molto velocemente, ed ero in estasi. Fino a quando non volevo fare qualche test. Il primo test che volevo eseguire era il mio MainActivity.

nel metodo onCreate sopra, si inietta con la LocalBroadcastManager da AndroidModule, al posto di quello da MainActivityTest, perché al momento non abbiamo un modo di raccontare la PhoneApplication o le attività che dovrebbero utilizzare un diverso oggetto grafico.

public class MainActivityTest extends 
     ActivityInstrumentationTestCase2<MainActivity> { 

    @Inject 
    NotificationManager notificationManager; 

    @Inject 
    ContentResolver contentResolver; 

    @Inject 
    MockContentResolver mockContentResolver; 

    @Inject 
    LocalBroadcastManager localBroadcastManager; 

    private Context context; 

    public MainActivityTest() { 
     super(MainActivity.class); 
    } 

    @Module(injects = { MainActivityTest.class, MainActivity.class }, library = true, overrides = true) 
    static class MockModule { 
     Context context; 

     public MockModule(Context context) { 
      this.context = context; 
     } 

     @Provides 
     @Singleton 
     ContentResolver provideContentResolver() { 
      return provideMockContentResolver(); 
     } 

     @Provides 
     @Singleton 
     MockContentResolver provideMockContentResolver() { 
      return new MockContentResolver(); 
     } 

     @Provides 
     @Singleton 
     LocalBroadcastManager provideLocalBroadcastManager() { 
      return Mockito.mock(LocalBroadcastManager.class); 
     } 
    } 

    @Override 
    protected void setUp() throws Exception { 
     System.setProperty("dexmaker.dexcache", getInstrumentation() 
       .getTargetContext().getCacheDir().getPath()); 

     context = getInstrumentation().getTargetContext(); 
     ObjectGraph graph = ObjectGraph.create(new AndroidModule(context), 
       new MockModule(context)); 
     graph.inject(this); 

     super.setUp(); 
    }; 

    @MediumTest 
    @UiThreadTest 
    public void testIncomingMessageReceiver_onReceive() 
      throws MalformedMimeTypeException { 

     ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor 
       .forClass(BroadcastReceiver.class); 
     Mockito.verify(localBroadcastManager, Mockito.atLeastOnce()) 
       .registerReceiver(receiverCaptor.capture(), 
         Mockito.any(IntentFilter.class)); 
    } 
} 

Questo è un test davvero semplice per iniziare. So che su onCreate registreremo un BroadcastReceiver, quindi assicuriamoci che sia registrato. Poiché il test ha il mockLocalBroadcastManager, ma l'attività utilizza LocalBroadcastManager di produzione, la verifica ha esito negativo.

+0

Non sono un grande esperto in test strumentali. Puoi controllare cosa viene chiamato prima - 'setUp' per test o' onCreate' per attività? –

+0

Sei sicuro che MainActivity sia stato creato e onCreate() venga chiamato? Secondo i documenti, è necessario invocare [getActivity()] (http://developer.android.com/reference/android/test/ActivityInstrumentationTestCase2.html#getActivity()) dal metodo di test per farlo. Inoltre, hai disconnesso il nome della classe del destinatario della trasmissione trasmessa e verifica che stia effettivamente utilizzando la configurazione di produzione anziché la configurazione di sostituzione che fornisce la simulazione? –

risposta

0

Non so per certo. Ho appena cercato sul web per scoprire come usare correttamente il pugnale per i test.

Tuttavia, come ho capito, il MainActivity prende il suo oggetto grafico dall'applicazione. Quindi, questo è il posto in cui devi inserire il tuo MockModule.

Per fare ciò, è necessario creare una sottoclasse di PhoneApplication e sovrascrivere il metodo getModules() per restituire il valore MockModule. Dopodiché dovresti prendere in giro l'applicazione con ActivityUnitTestCase.setApplication() (il tuo test dovrebbe prima sottoclasse ActivityUnitTestCase). Questo dovrebbe risolvere il problema.

Problemi correlati