Cómo burlar getApplicationContext

Tengo una aplicación que almacena la 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 extiende la clase Application.

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

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

El problema es que no sé cómo burlarme del contexto de la aplicación. Estoy utilizando ActivityUnitTestCase como una base de caso de prueba. Cuando llamo a setApplication , cambia el valor del miembro de mApplication de la clase Activity , pero no del contexto de la aplicación. He intentado setActivityContext también, pero parece incorrecto (no es el contexto de la aplicación, pero el contexto de la actividad) y se dispara afirmar dentro startActivity ).

Así que la pregunta es – ¿Cómo burlar getApplicationContext () ?

Dado que el método getApplicationContext está dentro de la clase que está extendiendo, se vuelve algo problemático. Hay un par de problemas a considerar:

  • Usted realmente no puede burlarse de una clase que está bajo prueba, que es uno de los muchos inconvenientes con la herencia de objetos (es decir, subclase).
  • El otro problema es que ApplicationContext es un singleton , lo que hace que todo sea más malo para probar, ya que no se puede burlar fácilmente de un estado global que está programado para ser insustituible.

Lo que puede hacer en esta situación es preferir la composición de objetos sobre la herencia . Así que para hacer que su Activity comprobable, necesita dividir un poco la lógica. Digamos que su Activity se llama MyActivity . Tiene que estar compuesto de un componente lógico (o clase), lo que se MyActivityLogic . Aquí está una figura simple del diagrama de la clase:

MyActivity y MyActivityLogic diagrama UML de yUml

Para resolver el problema singleton, dejamos que la lógica sea "inyectada" con un contexto de aplicación, por lo que se puede probar con un simulacro. Entonces sólo necesitamos probar que el objeto MyActivity ha puesto el contexto de la aplicación correcta en MyActivityLogic . ¿Cómo básicamente resolver ambos problemas es a través de otra capa de abstracción (parafraseado de Butler Lampson). La nueva capa que agregamos en este caso es la actividad lógica movida fuera del objeto de actividad.

Por el bien de su ejemplo, las clases tienen que verse así:

 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 debe ser algo como esto: Clases con métodos hechos en yUml

Para probar MyActivityLogic sólo necesitará un jUnit TestCase simple en lugar de ActivityUnitTestCase (ya que no es una actividad), y puede burlarse de su contexto de aplicación utilizando su marco de simulación de elección (ya que el manejo de sus propios simulacros es un poco de una fricción ). Ejemplo utiliza 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 MyActivity se utiliza ActivityUnitTestCase como de costumbre, solo necesitamos asegurarnos de que crea un MyActivityLogic con el ApplicationContext correcto. 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.

  • JUnit prueba para la aplicación de Android con fragmentos
  • (Unidad) Prueba de ArrayAdapter
  • Prueba de DialogFragments con Robolectric
  • Cómo ejecutar grupos de pruebas de unidad con gradle en Android
  • ¿Cómo probar la clase usando resolver contenido / proveedor?
  • ¿Qué probar con Robolectric?
  • ¿Qué hace testAndroidTestCaseSetUpProperly hacer
  • Obtener resultado de una actividad después de terminar (); En una prueba de unidad de Android
  • Unidad de prueba SparseArray utilizando JUnit (utilizando JVM)
  • Pruebas de instrumentación Android con Mockito
  • Android Studio Unit Testing: incapaz de encontrar instrumentación O clase no encontrada ex
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.