Hacer que un método simulado devuelva un argumento que se le pasó

675

Considere una firma de método como:

public String myFunction(String abc);

¿Puede Mockito ayudar a devolver la misma cadena que recibió el método?

Abhijeet Kashnia
fuente
Ok, ¿qué tal cualquier marco burlón de Java en general ... ¿Es esto posible con cualquier otro marco, o debería crear un trozo tonto para imitar el comportamiento que quiero?
Abhijeet Kashnia

Respuestas:

1001

Puedes crear una respuesta en Mockito. Supongamos que tenemos una interfaz llamada Aplicación con un método myFunction.

public interface Application {
  public String myFunction(String abc);
}

Aquí está el método de prueba con una respuesta Mockito:

public void testMyFunction() throws Exception {
  Application mock = mock(Application.class);
  when(mock.myFunction(anyString())).thenAnswer(new Answer<String>() {
    @Override
    public String answer(InvocationOnMock invocation) throws Throwable {
      Object[] args = invocation.getArguments();
      return (String) args[0];
    }
  });

  assertEquals("someString",mock.myFunction("someString"));
  assertEquals("anotherString",mock.myFunction("anotherString"));
}

Desde Mockito 1.9.5 y Java 8, también puede usar una expresión lambda:

when(myMock.myFunction(anyString())).thenAnswer(i -> i.getArguments()[0]);
Steve
fuente
1
Esto es lo que estaba buscando también. ¡Gracias! Sin embargo, mi problema era diferente. Quiero burlarme de un servicio de persistencia (EJB) que almacena objetos y los devuelve por su nombre.
migu
77
Creé una clase adicional que envuelve la creación de la respuesta. Entonces el código se lee comowhen(...).then(Return.firstParameter())
SpaceTrucker
69
Con Java 8 lambdas es fácil devolver el primer argumento, incluso para una clase específica, es decir when(foo(any()).then(i -> i.getArgumentAt(0, Bar.class)). Y también puede usar una referencia de método y llamar a un método real.
Paweł Dyda
Esto resuelve mi problema con un método de devoluciones Iterator<? extends ClassName>que causa todo tipo de problemas de conversión en una thenReturn()declaración.
Michael Shopsin
16
Con Java 8 y Mockito <1.9.5 a continuación, se convierte en la respuesta de Pawełwhen(foo(any()).thenAnswer(i -> i.getArguments()[0])
Graeme Musgo
567

Si tiene Mockito 1.9.5 o superior, hay un nuevo método estático que puede hacer el Answerobjeto por usted. Necesitas escribir algo como

import static org.mockito.Mockito.when;
import static org.mockito.AdditionalAnswers.returnsFirstArg;

when(myMock.myFunction(anyString())).then(returnsFirstArg());

o alternativamente

doAnswer(returnsFirstArg()).when(myMock).myFunction(anyString());

Tenga en cuenta que el returnsFirstArg()método es estático en la AdditionalAnswersclase, que es nuevo en Mockito 1.9.5; entonces necesitará la importación estática correcta.

Dawood ibn Kareem
fuente
17
Nota: es when(...).then(returnsFirstArg()), erróneamente tuve lo when(...).thenReturn(returnsFirstArg())que diojava.lang.ClassCastException: org.mockito.internal.stubbing.answers.ReturnsArgumentAt cannot be cast to
Benedikt Köppel
1
Nota: devuelveFirstArg () devuelve Respuesta <> en lugar del valor del argumento. Obtuve 'Foo (java.lang.String) no se puede aplicar a' (org.mockito.stubbing.Answer <java.lang.Object>) 'al intentar llamar a .thenReturn (new Foo (returnsFirstArg ()))
Lu55
Siempre necesito googlear esta respuesta una y otra vez durante los últimos años, ya que no puedo recordar "Respuestas adicionales" y solo la necesito muy raramente. Luego me pregunto cómo diablos puedo construir ese escenario, ya que no puedo encontrar las dependencias necesarias. ¿No podría esto agregarse directamente a mockito? : /
BAERUS
2
La respuesta de Steve es más genérica. Este solo le permite devolver el argumento sin formato. Si desea procesar ese argumento y devolver el resultado, las reglas de respuesta de Steve. Voté a ambos porque ambos son útiles.
akostadinov
Para su información, tenemos que importar static org.mockito.AdditionalAnswers.returnsFirstArg. esto para usar returnFirstArg. Además, puedo hacerlo when(myMock.myFunction(any())).then(returnsFirstArg())en Mockito 2.20. *
gtiwari333
78

Con Java 8 es posible crear una respuesta de una línea incluso con una versión anterior de Mockito:

when(myMock.myFunction(anyString()).then(i -> i.getArgumentAt(0, String.class));

Por supuesto, esto no es tan útil como el uso AdditionalAnswerssugerido por David Wallace, pero podría ser útil si desea transformar el argumento "sobre la marcha".

Paweł Dyda
fuente
1
Brillante. Gracias. Si el argumento es long, ¿esto todavía puede funcionar con el boxeo y Long.class?
vikingsteve 01 de
1
.getArgumentAt (..) no fue encontrado para mí pero .getArgument (1) funcionó (mockito 2.6.2)
Curtis Yallop
41

Tuve un problema muy similar. El objetivo era burlarse de un servicio que persiste Objetos y puede devolverlos por su nombre. El servicio se ve así:

public class RoomService {
    public Room findByName(String roomName) {...}
    public void persist(Room room) {...}
}

El simulacro de servicio utiliza un mapa para almacenar las instancias de Room.

RoomService roomService = mock(RoomService.class);
final Map<String, Room> roomMap = new HashMap<String, Room>();

// mock for method persist
doAnswer(new Answer<Void>() {
    @Override
    public Void answer(InvocationOnMock invocation) throws Throwable {
        Object[] arguments = invocation.getArguments();
        if (arguments != null && arguments.length > 0 && arguments[0] != null) {
            Room room = (Room) arguments[0];
            roomMap.put(room.getName(), room);
        }
        return null;
    }
}).when(roomService).persist(any(Room.class));

// mock for method findByName
when(roomService.findByName(anyString())).thenAnswer(new Answer<Room>() {
    @Override
    public Room answer(InvocationOnMock invocation) throws Throwable {
        Object[] arguments = invocation.getArguments();
        if (arguments != null && arguments.length > 0 && arguments[0] != null) {
            String key = (String) arguments[0];
            if (roomMap.containsKey(key)) {
                return roomMap.get(key);
            }
        }
        return null;
    }
});

Ahora podemos ejecutar nuestras pruebas en este simulacro. Por ejemplo:

String name = "room";
Room room = new Room(name);
roomService.persist(room);
assertThat(roomService.findByName(name), equalTo(room));
assertNull(roomService.findByName("none"));
migu
fuente
34

Con Java 8, la respuesta de Steve puede convertirse

public void testMyFunction() throws Exception {
    Application mock = mock(Application.class);
    when(mock.myFunction(anyString())).thenAnswer(
    invocation -> {
        Object[] args = invocation.getArguments();
        return args[0];
    });

    assertEquals("someString", mock.myFunction("someString"));
    assertEquals("anotherString", mock.myFunction("anotherString"));
}

EDITAR: aún más corto:

public void testMyFunction() throws Exception {
    Application mock = mock(Application.class);
    when(mock.myFunction(anyString())).thenAnswer(
        invocation -> invocation.getArgument(0));

    assertEquals("someString", mock.myFunction("someString"));
    assertEquals("anotherString", mock.myFunction("anotherString"));
}
yiwei
fuente
6

Esta es una pregunta bastante antigua, pero creo que sigue siendo relevante. Además, la respuesta aceptada solo funciona para String. Mientras tanto, hay Mockito 2.1 y algunas importaciones han cambiado, así que me gustaría compartir mi respuesta actual:

import static org.mockito.AdditionalAnswers.returnsFirstArg;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

@Mock
private MyClass myClass;

// this will return anything you pass, but it's pretty unrealistic
when(myClass.myFunction(any())).then(returnsFirstArg());
// it is more "life-like" to accept only the right type
when(myClass.myFunction(any(ClassOfArgument.class))).then(returnsFirstArg());

La función myClass.myFunction se vería así:

public class MyClass {
    public ClassOfArgument myFunction(ClassOfArgument argument){
        return argument;
    }  
}
LazR
fuente
4

Yo uso algo similar (básicamente es el mismo enfoque). A veces es útil que un objeto simulado devuelva resultados predefinidos para ciertas entradas. Eso va así:

private Hashtable<InputObject,  OutputObject> table = new Hashtable<InputObject, OutputObject>();
table.put(input1, ouput1);
table.put(input2, ouput2);

...

when(mockObject.method(any(InputObject.class))).thenAnswer(
       new Answer<OutputObject>()
       {
           @Override
           public OutputObject answer(final InvocationOnMock invocation) throws Throwable
           {
               InputObject input = (InputObject) invocation.getArguments()[0];
               if (table.containsKey(input))
               {
                   return table.get(input);
               }
               else
               {
                   return null; // alternatively, you could throw an exception
               }
           }
       }
       );
martín
fuente
4

Es posible que desee utilizar verificar () en combinación con ArgumentCaptor para asegurar la ejecución en la prueba y ArgumentCaptor para evaluar los argumentos:

ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
verify(mock).myFunction(argument.capture());
assertEquals("the expected value here", argument.getValue());

El valor del argumento es obviamente accesible a través del argumento.getValue () para una mayor manipulación / verificación / lo que sea.

fl0w
fuente
3

Esto es un poco viejo, pero vine aquí porque tuve el mismo problema. Estoy usando JUnit pero esta vez en una aplicación Kotlin con mockk. Estoy publicando una muestra aquí para referencia y comparación con la contraparte de Java:

@Test
fun demo() {
  // mock a sample function
  val aMock: (String) -> (String) = mockk()

  // make it return the same as the argument on every invocation
  every {
    aMock.invoke(any())
  } answers {
    firstArg()
  }

  // test it
  assertEquals("senko", aMock.invoke("senko"))
  assertEquals("senko1", aMock.invoke("senko1"))
  assertNotEquals("not a senko", aMock.invoke("senko"))
}
Lachezar Balev
fuente
2

Puede lograr esto usando ArgumentCaptor

Imagine que tiene la función de frijol así.

public interface Application {
  public String myFunction(String abc);
}

Luego, en tu clase de prueba:

//Use ArgumentCaptor to capture the value
ArgumentCaptor<String> param = ArgumentCaptor.forClass(String.class);


when(mock.myFunction(param.capture())).thenAnswer(new Answer<String>() {
    @Override
    public String answer(InvocationOnMock invocation) throws Throwable {
      return param.getValue();//return the captured value.
    }
  });

O si eres fanático de lambda simplemente haz:

//Use ArgumentCaptor to capture the value
ArgumentCaptor<String> param = ArgumentCaptor.forClass(String.class);


when(mock.myFunction(param.capture()))
    .thenAnswer((invocation) -> param.getValue());

Resumen: utilice argumentocaptor, para capturar el parámetro pasado. Más tarde en respuesta, devuelva el valor capturado usando getValue

Cyril Cherian
fuente
Esto no funciona (¿ya?). Con respecto a los documentos: este método debe usarse dentro de la verificación. Eso significa que solo puede capturar el valor cuando usa el método de verificación
Muhammed Misir
1. No estoy seguro de lo que quieres decir con This doesn´t work (anymore?).que tengo esto trabajando en mi instancia. 2. Lo siento, no tengo claro el punto que intentas hacer. La respuesta es específica a la pregunta de OP.
Cyril Cherian