2011-04-16 13 views
14

Tengo una aplicación que almacena información de contexto de la aplicación. La información de contexto de la aplicación se comparte entre las actividades de la clase MyApp que amplía la clase de aplicación.Cómo simular getApplicationContext

Estoy escribiendo una prueba unitaria para mi actividad, y quiero comprobar que cuando el usuario haga clic en un botón de la actividad, el estado de la aplicación cambiará. Algo como esto:

@Override 
public void onClick(View pView) { 
    ((MyApp)getApplicationContext()).setNewState(); 
} 

El problema es que no sé cómo burlarse de ese contexto de aplicación. Estoy usando ActivityUnitTestCase como una base de caso de prueba. Cuando llamo a setApplication, cambia el valor de mApplication miembro de Actividad clase, pero no contexto de la aplicación. He intentado setActivityContext también, pero parece estar mal (no es el contexto de la aplicación sino el contexto de la actividad) y se activa dentro de startActivity).

Entonces la pregunta es: ¿cómo burlarse de getApplicationContext()?

+0

He tenido la idea de reemplazar _getApplicationContext() _ con _getApplication() _. Ahora puedo simular el objeto _Application_ y usar _setApplication() _. Es un poco de solución. Sin embargo, no entiendo la diferencia entre esos métodos. Y la [pregunta] (http://stackoverflow.com/questions/5018545) relacionada con eso no es respondida. – lstipakov

Respuesta

30

Dado que el método getApplicationContext se encuentra dentro de la clase que está ampliando, se convierte en algo problemático. Hay un par de problemas a tener en cuenta:

  • Usted realmente no puede burlarse de una clase que está bajo prueba, que es uno de los muchos inconvenientes con herencia de objetos (es decir, la subclasificación).
  • El otro problema es que ApplicationContext es singleton, lo que lo hace más difícil de probar ya que no se puede simular fácilmente un estado global que está programado para ser irremplazable.

Lo que puede hacer en esta situación es a preferir object composition over inheritance. Por lo tanto, para que tu Activity se pueda probar, debes dividir la lógica un poco. Digamos que su Activity se llama MyActivity. Tiene que ser compuesto de un componente lógico (o clase), llámelo MyActivityLogic. Aquí es una figura simple clase diagrama:

MyActivity and MyActivityLogic UML diagram from yUml

para resolver el problema Singleton, dejamos que la lógica sea "inyecta" con un contexto de aplicación, por lo que se puede probar con una maqueta. Entonces solo tenemos que probar que el objeto MyActivity haya puesto el contexto de aplicación correcto en MyActivityLogic. Cómo resolvemos básicamente ambos problemas es a través de another layer of abstraction (parafraseado de Butler Lampson). La nueva capa que agregamos en este caso es la lógica de actividad movida fuera del objeto de actividad.

Por el bien de tu ejemplo las clases tienen que mirar clase-de la siguiente manera:

public final class MyActivityLogic { 

    private MyApp mMyApp; 

    public MyActivityLogic(MyApp pMyApp) { 
     mMyApp = pMyApp; 
    } 

    public MyApp getMyApp() { 
     return mMyApp; 
    } 

    public void onClick(View pView) { 
     getMyApp().setNewState(); 
    } 
} 

public final class MyActivity extends Activity { 

    // The activity logic is in mLogic 
    private final MyActivityLogic mLogic; 

    // Logic is created in constructor 
    public MyActivity() { 
     super(); 
     mLogic = new MyActivityLogic(
      (MyApp) getApplicationContext()); 
    } 

    // Getter, you could make a setter as well, but I leave 
    // that as an exercise for you 
    public MyActivityLogic getMyActivityLogic() { 
     return mLogic; 
    } 

    // The method to be tested 
    public void onClick(View pView) { 
     mLogic.onClick(pView); 
    } 

    // Surely you have other code here... 

} 

todo debería ser algo como esto: classes with methods made in yUml

Para probar MyActivityLogic no necesitará utilizar una jUnit simple TestCase en lugar de ActivityUnitTestCase (ya que no es una Actividad), y puede simular el contexto de la aplicación utilizando su marco de referencia preferido (ya que manual su propio simulacro es un poco molesto). Ejemplos de uso Mockito:

MyActivityLogic mLogic; // The CUT, Component Under Test 
MyApplication mMyApplication; // Will be mocked 

protected void setUp() { 
    // Create the mock using mockito. 
     mMyApplication = mock(MyApplication.class); 
    // "Inject" the mock into the CUT 
     mLogic = new MyActivityLogic(mMyApplication); 
} 

public void testOnClickShouldSetNewStateOnAppContext() { 
    // Test composed of the three A's   
    // ARRANGE: Most stuff is already done in setUp 

    // ACT: Do the test by calling the logic 
    mLogic.onClick(null); 

    // ASSERT: Make sure the application.setNewState is called 
    verify(mMyApplication).setNewState(); 
} 

Para probar la MyActivity utiliza ActivityUnitTestCase como de costumbre, sólo tenemos que asegurarnos de que se crea un MyActivityLogic con el correcto ApplicationContext. Ejemplo de código de prueba incompleto que hace todo esto:

// ARRANGE: 
MyActivity vMyActivity = getActivity(); 
MyApp expectedAppContext = vMyActivity.getApplicationContext(); 

// ACT: 
// No need to "act" much since MyActivityLogic object is created in the 
// constructor of the activity 
MyActivityLogic vLogic = vMyActivity.getMyActivityLogic(); 

// ASSERT: Make sure the same ApplicationContext singleton is inside 
// the MyActivityLogic object 
MyApp actualAppContext = vLogic.getMyApp(); 
assertSame(expectedAppContext, actualAppContext); 

Espero que todo tenga sentido para ti y te ayude.

+0

¡Gracias por la respuesta tan detallada! – lstipakov

+0

Gran respuesta. De hecho, su patrón introductor MVP para Android :) – Snicolas

+0

Esta es una excelente respuesta. Mantener la teoría OO es impresionante. – Thom

Cuestiones relacionadas