Mockito: ¿cómo verificar que se llamó al método en un objeto creado dentro de un método?
Soy nuevo en Mockito.
Dada la clase siguiente, ¿cómo puedo usar Mockito para verificar que someMethod
se invocó exactamente una vez después foo
de invocarse?
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();
¿Dónde bar
hay una instancia simulada de Bar
.
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á 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 tu método de prueba puedes 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 (Test Driven Development) puede impulsar el diseño de su código.
La respuesta clásica es: "No lo haces". Usted prueba la API pública de Foo
, no sus partes internas.
¿Hay algún comportamiento del Foo
objeto (o, menos bueno, de algún otro objeto en el entorno) que se vea afectado por foo()
? Si es así, pruébalo. Y si no, ¿para qué sirve el método?
Creo que Mockito @InjectMocks
es el camino a seguir.
Dependiendo de tu intención puedes utilizar:
- inyección de constructor
- Inyección de establecimiento de propiedad
- 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();
}
}
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 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 le permite a la persona que llama configurar el objeto de barra.
Espero eso ayude.