2012-03-31 10 views
7

¿Es posible utilizar Mockito y opcionalmente Powermock para simular una superclase S de forma que se burlen todas las llamadas a la superclase a S (incluidas las llamadas al constructor S())? Entonces, si utilizo el ejemplo siguiente, si reemplazo S con MockS usando Mockito, ¿utilizará la llamada al super() el constructor en MockS?¿Puedo simular el constructor de una superclase con Mockito/Powermock?

class S { 
    S() { 
     // Format user's hard drive, call 911, and initiate self-destruct 
    } 
} 

class T extends S { 
    T() { 
     super(); 
    } 
} 

class Test { 
    @Mock private S mockS; 
    new T(); // T's call to super() should call the mock, not the destructive S. 
} 

que he visto preguntas acerca de burlarse de los métodos individuales en S o burlarse sólo llama a super(), y leer que este no es compatible, pero no es claro si puedo o no burlarse de toda la superclase.

Con mis pruebas actuales, cuando trato de burlarme S, T llama a super() y llama a la implementación real, no al simulacro.

+0

cómo funciona su súper maqueta llamada()? ¿Puedes dar un ejemplo de código? – nansen

+0

@nansen No estoy llamando 'super()' desde el simulacro, el simulacro es la superclase. Por favor mira mis ediciones –

+1

No me gusta esa clase 'S' mucho. – DerMike

Respuesta

4

Para evitar esta aparente limitación, refactoré mi código, replacing inheritance with delegation, y creo que he terminado con un mejor diseño de todos modos ya que la herencia no era realmente necesaria.

El nuevo código tiene este aspecto. Tenga en cuenta que el código para la pregunta se simplificó, por lo que las clases reales tienen mucha más funcionalidad.

class S { 
    S() { 
     // Format user's hard drive, call 911, and initiate self-destruct 
    } 
} 

class T { 
    T(S s) {} // Now T "has an S" instead of "is an S" 
} 

class Test { 
    @Mock private S mockS; 
    new T(s); // T's call to super() should call the mock, not the destructive S. 
} 

Para los interesados, utilizando Guice y Android, la prueba se parece más a esto:

class T { 
    T(Activity activity, S s) {} 
} 

class Test { 
    @Mock Activity activity; 
    @Mock S mockS; 
    injector = Guice.createInjector(new AbstractModule() { 
    @Override protected void configure() { 
     bind(Activity.class).toInstance(activity); 
     bind(S.class).toInstance(mockS); 
    }} 
); 
    T t = injector.getInstance(T.class); 
} 
+0

Hola, estoy tratando de lograr algo similar a lo que se describe aquí, estoy probando este pero muestra un error de compilación en BindEverythingElse(), no estoy seguro de dónde es esta clase, incluí el guice-2.0 dependencia. ¿Debo agregar alguna otra dependencia adicional o me estoy perdiendo algo? ¡¡Gracias!! – Rishi

+0

@Rishi si no tiene otros enlaces personalizados, simplemente elimine esa línea. Como cuestión de hecho, es irrelevante para la respuesta, por lo que lo estoy eliminando. –

3

Creo que esto es posible con PowerMock solo si el método en el niño es diferente del método en la superclase (es decir, no se puede simular el método principal si el niño anula ese método). Para un poco más de detalle, puede mirar the relevant bug report.

Para PowerMock, consulte Suppressing Unwanted Behavior page para ver si será suficiente para sus necesidades.


Después de mucho excavar alrededor, que terminé usando JMockit para estos casos difíciles. Antes de pasar a JMockit, traté de anular todos los lugares donde se lanzaron excepciones mediante la supresión. Al final, tuve que anular algunos métodos, y no solo suprimirlos, así que terminé abandonando.

Ejemplo de uso para el caso de Android:

En primer lugar, te burlas a cabo su superclase utilizando el @MockClass anotación:

@MockClass(realClass = Activity.class, instantiation = PerMockedInstance) 
public class FakeActivity { 
    public Bundle mSavedInstanceState; 

    @Mock 
    public void $init() {} 

    @Mock 
    public void onCreate(Bundle savedInstanceState) { 
     mSavedInstanceState = savedInstanceState; 
    } 
} 

Cuando se activa, esta clase va a reemplazar el constructor por defecto de Activity con $init(), y reemplazar el método onCreate con el anterior. Con android, la unidad bajo prueba se deriva de Activity (en mi código de muestra, es HelloTestActivity). La clase de prueba es el siguiente:

public class HelloTestActivityTest3 extends AndroidTest { 
    @Tested 
    HelloTestActivity activity; 

    FakeActivity fakeActivity = new FakeActivity(); 

    @Before 
    public void setupMocks() 
    { 
     Mockit.setUpMock(fakeActivity); 
    } 

    @Test 
    public void onCreate_bundle(@Mocked Bundle savedInstanceState) 
    { 
     // Try to access out-of-band information from the fake 
     activity.onCreate(savedInstanceState); 
     assertSame(savedInstanceState, fakeActivity.mSavedInstanceState); 
    } 
} 

La llamada Mockit.setupMock(fakeActivity) sustituye a la superclase con mi instancia de la falsificación. Con este uso, también puedes acceder al estado interno de tu clase falsa. Si no necesita anular ningún método con funcionalidad personalizada, puede usar otros métodos disponibles de la clase Mockit.

Como señaló rogerio en los comentarios a continuación, burlarse de la clase Activity es lo mínimo. El siguiente código demuestra esto.

public class HelloTestActivityTest4 { 
    @Tested 
    HelloTestActivity activity; 

    @Mocked 
    Activity base; 

    @Test 
    public void testOnCreate() throws Exception { 
     // Just make sure "Stub!" exception is not thrown. 
     activity.onCreate(null); 
    } 
} 

La declaración @Mocked Activity base; hace que todos los métodos (exceptuando inicializadores estáticos) de Activity clase y sus superclases ser burlado en los ensayos definidos en HelloActivityTest4.

+0

Este puede ser mi malentendido, pero estoy hablando de burlarse de toda la superclase, no solo de un método. Vea mi edición y hágamelo saber. –

+0

He usado JMockit para burlarme de la clase de actividad androide "falsa" donde cada método arroja la excepción "Stub!" cuando se llama desde JVM. Puede simular el súper, o reemplazar ciertos métodos con falsificaciones que realizan accesos directos y simular el resto, eliminar constructores o bloques de inicializadores estáticos, etc. La flexibilidad no está disponible en PowerMock. Pero leeré las modificaciones y revisaré mi respuesta si es necesario. – vhallac

+0

¿Todos estamos usando Powermock debido a Android? :) Ya me he burlado de 'Activity' (los inyecté con Roboguice y uní' Activity.class' a la instancia 'mockActivity'). En el caso de esta publicación SO, estaba probando una extensión a 'ArrayAdapter' y la generalicé a' S' en la pregunta. Para tener éxito, terminé reemplazando la herencia con delegación y luego burlando el delegado inyectado. –

1

Lo que puedes hacer es extraer el código 'peligroso' en tu constructor de superclase en un método no privado, luego usar Mockito spy en tu clase T y anular el comportamiento en ese método extraído.

Esto, por supuesto, violaría la encapsulación. Guava ofrece la anotación VisibleForTesting para tales casos.

+0

Con PowerMockito tiene PowerMockito.whenNew (S.class) .withNoArguments(). ThenReturn (mockedS); Esto funciona cuando llamas explícitamente a S() pero no a super(). Formateé mi disco duro;) – nansen

+1

Terminé reemplazando la herencia con la delegación y luego burlando el delegado. –

+0

que es mucho más agradable. +1 – nansen

Cuestiones relacionadas