example - test unitarios java mockito
Usando Mockito para aplastar y ejecutar métodos para probar (4)
He hecho un par de preguntas orientadas a jUnit y Mockito recientemente y todavía estoy luchando por entenderlo. Los tutoriales son todos para ejemplos muy simples, así que estoy luchando para escalar mis casos de prueba para que funcionen para mis clases.
Actualmente estoy intentando escribir algunos casos de prueba para un método que tengo en uno de mis agentes en una aplicación web. El método interactúa con un par de otros métodos dentro del agente para validar algunos objetos. Solo quiero probar este método ahora mismo.
Esto es lo que he tratado de hacer:
Crea un objeto Mockito de mi agente así:
MyProcessingAgent mockMyAgent = Mockito.mock(MyProcessingAgent.class);
Configura los talones (con suerte el término correcto) usando Mockito. Cuando así sea:
Mockito.when(mockMyAgent.otherMethod(Mockito.any(arg1)).thenReturn(requiredReturnArg);
Intenta ejecutar mi método así:
List myReturnValue = mockMyAgent.methodThatNeedsTestCase();
Esperaba cosas en myReturnValue
, pero recibí 0 en su lugar, así que traté de depurar. Cuando llamo al método, nunca se ejecuta. Tengo un punto de depuración en la primera línea del método que nunca se toca.
Si quiero ejecutar el código en un método de una clase, pero forzar otros métodos en la clase (uno que trata de interactuar con bases de datos en el mundo exterior) para devolver valores falsificados. ¿Es esto posible con Mockito?
Parece que mi método actual de enfoque no es un estilo de prueba correcto, pero no estoy seguro de cómo avanzar. ¿Me puedo burlar de mi clase y hacer que un método se ejecute de manera normal, mientras que otros métodos son modificados para devolver mis valores dados para que no tenga que lidiar con el acceso a los datos durante la prueba de este método?
Casi lo tienes. El problema es que la Clase bajo prueba ( CUT ) no está diseñada para pruebas unitarias, en realidad no ha sido TDD ''d.
Piensa en esto, de esta manera…
- Necesito probar una función de una clase. Llamémosla myFunction.
- Esa función realiza una llamada a una función en otra clase / servicio / base de datos
- Esa función también llama a otro método en el CUT
En la prueba unitaria
- Debe crear un CUT concreto o
@Spy
en él - Puede
@Mock
todas las demás clases / servicios / bases de datos (es decir, dependencias externas) - Podría aplastar la otra función llamada en el CUT pero no es realmente la forma en que se deben realizar las pruebas unitarias .
Para evitar ejecutar código que no está probando estrictamente, puede abstraer ese código en algo que pueda ser @Mock
ed.
En este ejemplo muy simple, una función que crea un objeto será difícil de probar
public void doSomethingCool(String foo) {
MyObject obj = new MyObject(foo);
// can''t do much with obj in a unit test unless it is returned
}
Pero una función que usa un servicio para obtener MyObject es fácil de probar, ya que hemos resumido lo difícil / imposible de probar el código en algo que hace que este método sea comprobable.
public void doSomethingCool(String foo) {
MyObject obj = MyObjectService.getMeAnObject(foo);
}
como MyObjectService puede ser burlado y también verificado que .getMeAnObject () se llama con la variable foo .
Entonces, la idea de burlarse de la clase bajo prueba es anathima a la práctica de prueba. No deberías hacer esto. Debido a que lo has hecho, tu prueba está ingresando a las clases de burla de Mockito, no a tu clase bajo prueba.
El espionaje tampoco funcionará porque esto solo proporciona una envoltura / proxy alrededor de la clase espiada. Una vez que la ejecución está dentro de la clase, no pasará por el proxy y, por lo tanto, no golpeará al espía. ACTUALIZACIÓN: aunque creo que esto es cierto para los proxies de Spring, parece no ser cierto para los espías Mockito. Configuré un escenario donde el método m1()
llama a m2()
. Veo el objeto y el talón m2()
para doNothing
hacer doNothing
. Cuando invoco m1()
en mi prueba, no se alcanza el m2()
de la clase. Mockito invoca el talón. Así que usar un espía para lograr lo que se pide es posible. Sin embargo, reiteraría que lo consideraría una mala práctica (IMHO).
Debes burlarte de todas las clases de las que depende la clase a prueba. Esto le permitirá controlar el comportamiento de los métodos invocados por el método bajo prueba, ya que controla la clase que invocan esos métodos.
Si su clase crea instancias de otras clases, considere usar fábricas.
Estás confundiendo un Mock
con un Spy
.
En un simulacro, todos los métodos se eliminan y devuelven "tipos de devolución inteligente". Esto significa que llamar a cualquier método en una clase simulada no hará nada a menos que especifique el comportamiento.
En un espía, la funcionalidad original de la clase aún está allí, pero puede validar las invocaciones de métodos en un espía y también anular el comportamiento del método.
Lo que quieres es
MyProcessingAgent mockMyAgent = Mockito.spy(MyProcessingAgent.class);
Un ejemplo rápido:
static class TestClass {
public String getThing() {
return "Thing";
}
public String getOtherThing() {
return getThing();
}
}
public static void main(String[] args) {
final TestClass testClass = Mockito.spy(new TestClass());
Mockito.when(testClass.getThing()).thenReturn("Some Other thing");
System.out.println(testClass.getOtherThing());
}
La salida es:
Some Other thing
NB: Debería intentar simular las dependencias para la clase que se está probando, no la clase en sí.
RESPUESTA CORTA
Cómo hacer en tu caso:
int argument = 5; // example with int but could be another type
Mockito.when(mockMyAgent.otherMethod(Mockito.anyInt()).thenReturn(requiredReturnArg(argument));
RESPUESTA LARGA
En realidad, lo que quiere hacer es posible, al menos en Java 8. Tal vez no recibió esta respuesta de otras personas porque estoy usando Java 8 que lo permite y esta pregunta es antes del lanzamiento de Java 8 (que permite pasar funciones , no solo valores a otras funciones).
Simulemos una llamada a una consulta de base de datos. Esta consulta devuelve todas las filas de HotelTable que tienen FreeRoms = X y StarNumber = Y. Lo que espero durante las pruebas, es que esta consulta devolverá una lista de diferentes hoteles: cada hotel devuelto tiene el mismo valor X e Y, mientras que Otros valores y los decidiré según mis necesidades. El siguiente ejemplo es simple pero, por supuesto, puede hacerlo más complejo.
Así que creo una función que devolverá resultados diferentes pero todos ellos tienen FreeRoms = X y StarNumber = Y.
static List<Hotel> simulateQueryOnHotels(int availableRoomNumber, int starNumber) {
ArrayList<Hotel> HotelArrayList = new ArrayList<>();
HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Rome, 1, 1));
HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Krakow, 7, 15));
HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Madrid, 1, 1));
HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Athens, 4, 1));
return HotelArrayList;
}
Tal vez Spy sea mejor (por favor intente), pero hice esto en una clase burlada. Aquí como hago (note los valores de anyInt ()):
//somewhere at the beginning of your file with tests...
@Mock
private DatabaseManager mockedDatabaseManager;
//in the same file, somewhere in a test...
int availableRoomNumber = 3;
int starNumber = 4;
// in this way, the mocked queryOnHotels will return a different result according to the passed parameters
when(mockedDatabaseManager.queryOnHotels(anyInt(), anyInt())).thenReturn(simulateQueryOnHotels(availableRoomNumber, starNumber));