Mockito: ¿cómo verificar el método se invocó en un objeto creado dentro de un método?

322

Soy nuevo en Mockito.

Dada la clase a continuación, ¿cómo puedo usar Mockito para verificar que someMethodse invocó exactamente una vez que foose invocó?

public class Foo
{
    public void foo(){
        Bar bar = new Bar();
        bar.someMethod();
    }
}

Me gustaría hacer la siguiente llamada de verificación,

verify(bar, times(1)).someMethod();

donde bares una instancia simulada de Bar.

mre
fuente
2
stackoverflow.com/questions/6520242/… - Pero no quiero usar PowerMock.
mre
Cambiar la API o PowerMock. Uno de los dos.
John B
¿Cómo cubrir algo como esto? inicio vacío público sincronizado (BundleContext bundleContext) produce Exception {BundleContext bc = bundleContext; logger.info ("INICIANDO EL PAQUETE DE SERVICIO HTTP"); this.tracker = nuevo ServiceTracker (bc, HttpService.class.getName (), null) {@Override public Object agregarService (ServiceReference serviceRef) {httpService = (HttpService) super.addingService (serviceRef); registerServlets (); devolver httpService; }}}
ShAkKiR

Respuestas:

366

Inyección de dependencia

Si inyecta la instancia de Bar, o una fábrica que se utiliza para crear la instancia de Bar (o una de las otras 483 formas de hacerlo), tendría el acceso necesario para realizar la prueba.

Ejemplo de fábrica:

Dada una clase de Foo escrita así:

public class Foo {
  private BarFactory barFactory;

  public Foo(BarFactory factory) {
    this.barFactory = factory;
  }

  public void foo() {
    Bar bar = this.barFactory.createBar();
    bar.someMethod();
  }
}

en su método de prueba puede inyectar un BarFactory como este:

@Test
public void testDoFoo() {
  Bar bar = mock(Bar.class);
  BarFactory myFactory = new BarFactory() {
    public Bar createBar() { return bar;}
  };

  Foo foo = new Foo(myFactory);
  foo.foo();

  verify(bar, times(1)).someMethod();
}

Bonificación: este es un ejemplo de cómo TDD puede impulsar el diseño de su código.

csturtz
fuente
66
¿Hay alguna manera de hacer esto sin modificar la clase para las pruebas unitarias?
mre
66
Bar bar = mock(Bar.class)en lugar deBar bar = new Bar();
John B
77
no que yo supiese. pero, no estoy sugiriendo que modifique la clase solo para pruebas unitarias. Esta es realmente una conversación sobre código limpio y el SRP. O ... ¿es responsabilidad del método foo () en la clase Foo construir un objeto Bar. Si la respuesta es sí, entonces es un detalle de implementación y no debe preocuparse por probar la interacción específicamente (consulte la respuesta de @ Michael). Si la respuesta es no, está modificando la clase porque su dificultad en las pruebas es una señal de alerta de que su diseño necesita una pequeña mejora (de ahí la ventaja que agregué sobre cómo el diseño de las unidades TDD).
csturtz
3
¿Puedes pasar un objeto "real" al "verificar" de Mockito?
John B
44
También puede burlarse de la fábrica: BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
levsa
18

La respuesta clásica es: "No lo haces". Usted prueba la API pública deFoo , no sus componentes internos.

¿Hay algún comportamiento del Fooobjeto (o, menos bueno, algún otro objeto en el entorno) que se vea afectado foo()? Si es así, prueba eso. Y si no, ¿qué hace el método?

Michael Brewer-Davis
fuente
44
Entonces, ¿qué probarías realmente aquí? La API pública de Foois public void foo(), donde las partes internas solo están relacionadas con barras.
Behelit
15
Probar solo la API pública está bien, hasta que haya errores genuinos con efectos secundarios que necesiten pruebas. Por ejemplo, verificar que un método privado esté cerrando sus conexiones HTTP correctamente es excesivo hasta que descubra que el método privado no cerrando sus conexiones correctamente y, por lo tanto, está causando un problema masivo. En ese punto, Mockito y de verify()hecho se vuelven muy útiles, incluso si ya no estás adorando en el altar sagrado de las pruebas de integración.
Dawngerpony
@DuffJ No uso Java, pero eso suena como algo que tu compilador o herramienta de análisis de código debería detectar.
user247702
3
Estoy de acuerdo con DuffJ, aunque la programación funcional es divertida, llega un punto en el que su código interactúa con el mundo exterior. No importa si lo llama "aspectos internos", "efectos secundarios" o "funcionalidad", definitivamente desea probar esa interacción: si sucede y si sucede la cantidad correcta de veces y con los argumentos correctos. @Stijn: podría haber sido un mal ejemplo (pero si se abren varias conexiones, y solo se cierran algunas, entonces se vuelve interesante). Un mejor ejemplo sería verificar el clima si se hubieran enviado los datos correctos a través de la conexión.
Andras Balázs Lajtha
13

Si no quieres usar DI o Fábricas. Puedes refactorizar tu clase de una manera un poco complicada:

public class Foo {
    private Bar bar;

    public void foo(Bar bar){
        this.bar = (bar != null) ? bar : new Bar();
        bar.someMethod();
        this.bar = null;  // for simulating local scope
    }
}

Y tu clase de prueba:

@RunWith(MockitoJUnitRunner.class)
public class FooTest {
    @Mock Bar barMock;
    Foo foo;

    @Test
    public void testFoo() {
       foo = new Foo();
       foo.foo(barMock);
       verify(barMock, times(1)).someMethod();
    }
}

Entonces la clase que llama a tu método foo lo hará así:

public class thirdClass {

   public void someOtherMethod() {
      Foo myFoo = new Foo();
      myFoo.foo(null);
   }
}

Como puede ver al llamar al método de esta manera, no necesita importar la clase Bar en ninguna otra clase que esté llamando a su método foo, que tal vez sea algo que desee.

Por supuesto, la desventaja es que está permitiendo que la persona que llama configure el objeto de barra.

Espero eso ayude.

raspacorp
fuente
3
Creo que esto es un antipatrón. Las dependencias deben inyectarse, punto. Permitir una dependencia inyectada opcionalmente con el único fin de probar es evitar intencionalmente mejorar el código y probar intencionalmente algo diferente al código que se ejecuta en producción. Ambas son cosas horribles y horribles que hacer.
ErikE
8

Solución para su código de ejemplo usando PowerMockito.whenNew

  • mockito-all 1.10.8
  • powermock-core 1.6.1
  • powermock-module-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • junio 4.12

FooTest.java

package foo;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

//Both @PrepareForTest and @RunWith are needed for `whenNew` to work 
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Foo.class })
public class FooTest {

    // Class Under Test
    Foo cut;

    @Mock
    Bar barMock;

    @Before
    public void setUp() throws Exception {
        cut = new Foo();

    }

    @After
    public void tearDown() {
        cut = null;

    }

    @Test
    public void testFoo() throws Exception {

        // Setup
        PowerMockito.whenNew(Bar.class).withNoArguments()
                .thenReturn(this.barMock);

        // Test
        cut.foo();

        // Validations
        Mockito.verify(this.barMock, Mockito.times(1)).someMethod();

    }

}

Salida JUnit Salida JUnit

javaPlease42
fuente
8

Creo que Mockito @InjectMockses el camino a seguir.

Dependiendo de su intención, puede usar:

  1. Inyección de constructor
  2. Inyección del establecedor de propiedades
  3. Inyección de campo

Más información en documentos

A continuación se muestra un ejemplo con inyección de campo:

Clases

public class Foo
{
    private Bar bar = new Bar();

    public void foo() 
    {
        bar.someMethod();
    }
}

public class Bar
{
    public void someMethod()
    {
         //something
    }
}

Prueba:

@RunWith(MockitoJUnitRunner.class)
public class FooTest
{
    @Mock
    Bar bar;

    @InjectMocks
    Foo foo;

    @Test
    public void FooTest()
    {
        doNothing().when( bar ).someMethod();
        foo.foo();
        verify(bar, times(1)).someMethod();
    }
}
siulkilulki
fuente
3

Sí, si realmente quiere / necesita hacerlo, puede usar PowerMock. Esto debería considerarse un último recurso. Con PowerMock puede hacer que devuelva un simulacro de la llamada al constructor. Luego haga la verificación en el simulacro. Dicho esto, csturtz es la respuesta "correcta".

Aquí está el enlace a la construcción simulada de nuevos objetos

John B
fuente