Mockito: ¿cómo verificar que el método fue llamado en un objeto creado dentro de un método?


Soy nuevo en Mockito.

Dada la clase a continuación, ¿cómo puedo usar Mockito para verificar que someMethod fue invocado exactamente una vez después de que foo fue invocado?

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 bar es una instancia burlada de Bar.

Author: mre, 2012-03-23

7 answers

Inyección de dependencia

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

Ejemplo de fábrica:

Dada una clase 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 una BarFactory como esta:

@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();
}

Bono: Este es un ejemplo de cómo TDD puede conducir el diseño de su código.

 249
Author: csturtz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-13 13:40:28

La respuesta clásica es, "Usted no." Usted prueba la API pública de Foo, no sus internos.

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

 18
Author: Michael Brewer-Davis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-23 15:16:37

Si no desea utilizar 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 su 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 está llamando 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 establezca el Objeto Bar.

Espero que ayude.

 10
Author: raspacorp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-01 00:41:13

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

  • mockito-todos 1.10.8
  • powermock-core 1.6.1
  • powermock-module-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • junit 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();

    }

}

JUnit Output Salida de JUnit

 6
Author: javaPlease42,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-21 20:21:27

Creo que Mockito @InjectMocks es el camino a seguir.

Dependiendo de tu intención puedes usar:

  1. Inyección de constructor
  2. Inyección de setter de propiedades
  3. Inyección de campo

Más información en docs

A continuación se muestra un ejemplo con la 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();
    }
}
 6
Author: siulkilulki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-20 12:36:37

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

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

 3
Author: John B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-23 16:50:38

Otra forma sencilla sería añadir alguna instrucción log a la barra.someMethod() y luego asegúrese de que puede ver dicho mensaje cuando se ejecuta su prueba, vea ejemplos aquí: Cómo hacer una afirmación JUnit en un mensaje en un registrador

Eso es especialmente útil cuando su barra.someMethod() es private.

 0
Author: Nestor Milyaev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-26 11:40:50