2011-12-22 26 views
10

Así que me han pedido que lea sobre burla y BDD para nuestro equipo de desarrollo y jugar con simulacros para mejorar algunas de nuestras pruebas unitarias existentes (como un experimento).Mockito: Mocking "Blackbox" Dependencias

En última instancia, he optado por ir con Mockito por varias razones (algunas fuera del alcance de mi control), pero es porque admite el uso de stubbing y burlas para casos en que la burla no sería apropiada.

He pasado todo el día aprendiendo sobre Mockito, burlas (en general) y BDD. Y ahora estoy listo para profundizar y comenzar a aumentar nuestras pruebas unitarias.

así que tenemos una clase llamada WebAdaptor que tiene un método run():

public class WebAdaptor { 

    private Subscriber subscriber; 

    public void run() { 

     subscriber = new Subscriber(); 
     subscriber.init(); 
    } 
} 

Tenga en cuenta: (por razones fuera del alcance de esta pregunta) no tengo una manera de modificar el código . Así lo hago no tengo la capacidad de agregar un método setter para Subscriber, y por lo tanto, se puede considerar como un "blackbox" inalcanzable dentro de mi WebAdaptor.

Quiero escribir una prueba de unidad que incorpora una maqueta Mockito, y utiliza esa maqueta a verify que la ejecución de WebAdaptor::run() provoca Subscriber::init() a ser llamados.

Así que aquí es lo que tengo hasta ahora (en el interior WebAdaptorUnitTest):

@Test 
public void runShouldInvokeSubscriberInit() { 

    // Given 
    Subscriber mockSubscriber = mock(Subscriber.class); 
    WebAdaptor adaptor = new WebAdaptor(); 

    // When 
    adaptor.run(); 

    // Then 
    verify(mockSubscriber).init(); 
} 

Cuando corro esta prueba, el Subscriber::init() método actual es ejecutado (I puede decir de la salida de la consola y los archivos al ver que se genera en mi sistema local), no el mockSubscriber, que no debería hacer (ni devolver) nada.

He comprobado y vuelto a inspeccionar: init es public, no es ni static o final, y vuelve void. Según los documentos, Mockito no debería tener ningún problema para burlarse de este objeto.

Me hizo pensar: ¿necesito asociar explícitamente el mockSubscriber con el adaptor? Si este es el caso, entonces normalmente, la siguiente sería normalmente solucionarlo:

adaptor.setSubscriber(mockSubscriber); 

Pero ya que no puedo añadir cualquier colocador (por favor leer mi nota anterior), estoy en una pérdida en cuanto a cómo podría fuerza tal asociación. Entonces, varias preguntas muy relacionadas:

  • ¿Alguien puede confirmar que he configurado la prueba correctamente (usando la API de Mockito)?
  • ¿Es correcta mi sospecha sobre la falta del colocador? (¿Tengo que asociar estos objetos a través de un colocador?)
  • Si mi sospecha es cierta, y no puedo modificar WebAdaptor, ¿hay alguna posibilidad de que esté a mi disposición?

¡Gracias de antemano!

+0

Esto no responde directamente a su pregunta, pero JMockIt que hace que este tipo de recuadro negro burlarse bastante fácil. ¿JMockIt es una opción para ti? –

+0

¿Cómo se instancia el suscriptor en esta clase? ¿Es posible anular el código de creación de instancias para devolver una instancia que controlas? –

+0

run() es el único método que utiliza el suscriptor, por lo tanto, debe ser una variable local dentro de ese método. De nuevo, no puedo cambiar el código ... – IAmYourFaja

Respuesta

10

Necesita inyectar el simulacro en la clase que está probando.No necesita acceder al Suscriptor. La forma en que Mockito y otros marcos de burla ayudan es que no necesitas acceder a los objetos con los que estás interactuando. Sin embargo, necesitas una forma de obtener objetos simulados en la clase que estás probando.

public class WebAdaptor { 

    public WebAdaptor(Subscriber subscriber) { /* Added a new constructor */ 
     this.subscriber = subscriber; 
    } 

    private Subscriber subscriber; 

    public void run() { 
     subscriber.init(); 
    } 
} 

Ahora puede verificar sus interacciones en el simulacro, en lugar de en el objeto real.

@Test 
public void runShouldInvokeSubscriberInit() { 

    // Given 
    Subscriber mockSubscriber = mock(Subscriber.class); 
    WebAdaptor adaptor = new WebAdaptor(mockSubscriber); // Use the new constructor 

    // When 
    adaptor.run(); 

    // Then 
    verify(mockSubscriber).init(); 
} 

Si se añade el suscriptor al constructor no es el enfoque correcto, también se puede considerar el uso de una fábrica para permitir WebAdaptor crear instancias de nuevos objetos de suscriptor de una fábrica que controlas. A continuación, podría burlarse de la fábrica al proveedor simulacro de suscriptores.

+0

Su fragmento para WebAdaptor todavía tiene el método run() creando un nuevo suscriptor. FYI. –

+0

David V: después de escucharme en voz alta (mientras escribía esta pregunta), combinado con su respuesta y los comentarios anteriores, parece que cambiar el código de 'WebAdaptor' es mi única opción. ¡Gracias por su respuesta! – IAmYourFaja

+2

Para mantener su clase WebAdaptor trabajando con el código existente, es posible que también desee tener un constructor no-arg que llame a su nuevo constructor. Entonces, los usos no probables existentes de la clase pueden usar el constructor no-arg. Entonces, el nuevo constructor sería 'public WebAdaptor() {this (new Subscriber());}'. Además, el constructor que tiene el argumento Subscriber debe ser package-private. –

5

Si no desea cambiar el código de producción y aún así puede simular la funcionalidad de la clase de Suscriptor, debe echarle un vistazo a PowerMock. Funciona bien junto con Mockito y le permite simular la creación de nuevos objetos.

Subscriber mockSubscriber = mock(Subscriber.class); 
whenNew(Subscriber.class).withNoArguments().thenReturn(mockSubscriber); 

más información al respecto en el marco documentation for the PowerMock.

+0

Lea [Ejemplo # 3 - Simule la construcción de objetos nuevos] (http://blog.jayway.com/2009/10/28/untestable-code-with-mockito-and-powermock/) (desplácese hacia abajo) para obtener más información ejemplo exhaustivo. – matsev

2

Hay una manera de inyectar su simulacro en la clase bajo prueba sin hacer ninguna modificación al código. Esto se puede hacer usando el Mockito WhiteBox. Esta es una muy buena característica que se puede utilizar para inyectar las dependencias de su clase bajo prueba de sus pruebas. Lo que sigue es un ejemplo sencillo de cómo funciona,

@Mock 
Subscriber mockSubscriber; 
WebAdaptor cut = new WebAdaptor(); 

@Before 
public void setup(){ 
    //sets the internal state of the field in the class under test even if it is private 
    MockitoAnnotations.initMocks(this); 

    //Now the whitebox functionality injects the dependent object - mockSubscriber 
    //into the object which depends on it - cut 
    Whitebox.setInternalState(cut, "subscriber", mockSubscriber); 
} 

@Test 
public void runShouldInvokeSubscriberInit() { 
    cut.run(); 
    verify(mockSubscriber).init(); 
} 

Esperanza esto ayuda :-)

+0

@zharvey fue útil? o estaba equivocado? – Bala

+2

Esto no funcionará. Cuando se llama a corte.run() una instancia nueva, no simulada, del suscriptor se creará dentro de WebAdapter y reemplazará la versión simulada. El método init en la nueva instancia se llamará. –

+0

Sí, tienes razón. Me lo perdi. – Bala

1

No puede burlarse del suscriptor utilizando Mockito en su aplicación actual.

El problema que tiene es que el suscriptor se construye y luego se accede de inmediato, Mockito no tiene la capacidad de reemplazar (o espiar) la instancia del suscriptor después de la creación, pero antes de que se llame al método init.

La respuesta de David V resuelve esto agregando el suscriptor al constructor. Una alternativa que conserva la construcción oculta del suscriptor sería crear una instancia del suscriptor en un constructor no arg de WebAdapter y luego usar el reflejo para reemplazar esa instancia antes de llamar al método de ejecución.

Su WebAdapter se vería así,

public class WebAdaptor { 

    private Subscriber subscriber; 

    public WebAdaptor() { 
     subscriber = new Subscriber(); 
    } 

    public void run() {    
     subscriber.init(); 
    } 
} 

Y se podría utilizar ReflectionTestUtils del módulo de prueba de Spring Framework para inyectar dependencias en ese campo privado.

@Test 
public void runShouldInvokeSubscriberInit() { 

    // Given 
    Subscriber mockSubscriber = mock(Subscriber.class); 
    WebAdaptor adaptor = new WebAdaptor(); 
    ReflectionTestUtils.setField(adaptor "subscriber", mockSubscriber); 

    // When 
    adaptor.run(); // This will call mockSubscriber.init() 

    // Then 
    verify(mockSubscriber).init(); 
} 

ReflectionTestUtils es realmente sólo un envoltorio sobre la reflexión de Java, la misma se podría lograr de forma manual (y mucho más informa extensamente) sin la dependencia de primavera.

Mockito's WhiteBox (como lo sugiere Bala) funcionaría aquí en lugar de ReflectionTestUtils, está contenido dentro del paquete interno de Mockito, así que me alejo de él, YMMV.

2

Usted podría haber utilizado PowerMock burlarse de la llamada al constructor sin cambiar el código original:

import org.mockito.Mockito; 
import org.powermock.api.mockito.PowerMockito; 
import org.powermock.core.classloader.annotations.PrepareForTest; 
import org.powermock.modules.junit4.PowerMockRunner; 

@RunWith(PowerMockRunner.class) 
@PrepareForTest(WebAdaptor.class) 
public class WebAdaptorTest { 
    @Test 
    public void testRunCallsSubscriberInit() { 
     final Subscriber subscriber = mock(Subscriber.class); 
     whenNew(Subscriber.class).withNoArguments().thenReturn(subscriber); 
     new WebAdaptor().run(); 
     verify(subscriber).init(); 
    } 
}