12 DecSustitución fácil de colaboradores con Mockito

Acabo de probar Mockito y creo que por fin he encontrado el framework de mocks que necesitaba. Crear “stubs” para sustituir a los colaboradores en las pruebas con EasyMock o jMock me resultaba muy laborioso, en cambio con Mockito es muy fácil.

El ejemplo:

 @Test public void testComprarUnProducto() {  TerminalPuntoDeVenta tpv = new TerminalPuntoDeVenta(100,logger);

  CodigoProducto codigo = new CodigoProducto("FANTA 33CL","1234567890");  DispositivoEscaner mockEscaner = mock(DispositivoEscaner.class);  when(mockEscaner.scan()).thenReturn(codigo);  tpv.setEscaner(mockEscaner);

  RepositorioProductos mockRepositorioProductos = mock(RepositorioProductos.class);  Producto producto = new Producto(codigo);  producto.setPrecio(10);  when(mockRepositorioProductos.buscarProducto(codigo)).thenReturn(producto);  tpv.setRepositorioProductos(mockRepositorioProductos);

  tpv.setImpresora(mock(DispositivoTicket.class));

  tpv.iniciarCompra();  tpv.scan();  tpv.finalizarCompra();  double resultado = tpv.cerrarCaja();  assertEquals(110,resultado,0); }

Los métodos estáticos mock y when son la clave, especialmente when, porque en vez de liarnos a grabar expectations como con otros frameworks, con Mockito decimos cuando llamen al objeto sustituido a tal método y con tales parámetros, entonces devuelve tal resultado. Mucho más intuitivo.

Además, como estoy intentando “quitarme” de las pruebas que comprueban las colaboraciones, me viene genial; aunque si quisiera, no tengo más que hacer los verify que también haría con los otros.

Actualización:
He sacado un rato y he escrito el mismo ejemplo pero con EasyMock.

 @Test public void testComprarUnProductoConEasymock() {  TerminalPuntoDeVenta tpv = new TerminalPuntoDeVenta(100,logger);

  CodigoProducto codigo = new CodigoProducto("FANTA 33CL","1234567890");  DispositivoEscaner mockEscaner = createMock(DispositivoEscaner.class);  mockEscaner.scan();  expectLastCall().andReturn(codigo);  tpv.setEscaner(mockEscaner);

  RepositorioProductos mockRepositorioProductos = createMock(RepositorioProductos.class);  Producto producto = new Producto(codigo);  producto.setPrecio(10);  mockRepositorioProductos.buscarProducto(codigo);  expectLastCall().andReturn(producto);  tpv.setRepositorioProductos(mockRepositorioProductos);

  DispositivoTicket mockImpresora = createNiceMock(DispositivoTicket.class);  tpv.setImpresora(mockImpresora);

  replay(mockEscaner);  replay(mockImpresora);  replay(mockRepositorioProductos);

  tpv.iniciarCompra();  tpv.scan();  tpv.finalizarCompra();  double resultado = tpv.cerrarCaja();  assertEquals(110,resultado,0); }

18 FebProbar un objeto que instancia a sus propios colaboradores

Esta semana pasada he tenido el siguiente problema:
Queremos probar una clase (Flow) que implementa un flujo de llamadas a otros objetos (Tasks) que representan tareas.

En principio, parece un enunciado bastante simple, pero la dificultad radica en que no es posible sustituir las tareas por dobles (mocks) porque son instanciadas dentro del propio flujo. ¿Qué estrategia de prueba podemos seguir entonces?

Inicialmente lo hemos resuelto teniendo una TaskFactory que inyectamos al flujo, de tal modo que podamos hacer un doble de ésta (con EasyMock).

Pero este fin de semana, con algo más de tranquilidad, he estado reflexionando y me he dado cuenta de que realmente no estábamos siguiendo una buena estrategia, puesto que habíamos hecho un diseño contraintuitivo simplemente porque no éramos capaces de hacer pruebas unitarias del objeto Flow. Es más, en mi opinión, lo que estábamos probando era que habíamos escrito el código que estaba ya escrito… es decir, no estábamos realmente probando ni el resultado final ni las colaboraciones con objetos externos al propio SUT (System Under Test, que dice la bibliografía más reconocida). Estábamos probando que se ejecutaban las diferentes líneas del algoritmo. Mmmm… me parece que eso no es una prueba unitaria…

Pues bien, dándole vueltas al tema he llegado a la conclusión de que:

  • las tareas son parte del flujo, e.d., son parte del SUT
  • nuestra intención era probar las salidas indirectas del flujo
  • deberíamos comprobar las llamadas a sistemas externos que puedan hacer las tareas, e.d. necesitamos puntos de observación para esas llamadas
  • también puedo probar es el estado final del SUT, lo cuál me ha dado la pista de que puedo pasar un contexto (FlowContext) al flujo, de tal modo que cada una de las tareas pueda modificarlo y yo comprobar al final de cada prueba el resultado final (esto me viene bien, por cierto, para otros requisitos que tengo más adelante…)

20 JulEasyMock: IllegalStateException y matchers

Ayer estaba haciendo un test con EasyMock y me daba el siguiente error:

java.lang.IllegalStateException: 2 matchers expected, 1 recorded.
    at org.easymock.internal.ExpectedInvocation.createMissingMatchers(ExpectedInvocation.java:41)
    at org.easymock.internal.ExpectedInvocation.<init>(ExpectedInvocation.java:33)
    at org.easymock.internal.ExpectedInvocation.<init>(ExpectedInvocation.java:26)
    at org.easymock.internal.RecordState.invoke(RecordState.java:64)
    at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:24)
    at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:45)
    at $Proxy0.find(Unknown Source)
    at com.degesys.core.idm.logic.DTOFactoryImplTest.testGetUserDTO(DTOFactoryImplTest.java:190)
...

Resulta que mi código era como sigue:

EasyMock.expect(mockManager.find(LdapObject.class, EasyMock.anyObject())).andReturn(mockLdapObject);

“Gugleando” encontré lo siguiente:
http://marcels-javanotes.blogspot.com/2007/03/easymock-and-illegalstateexception.html

Y finalmente, en el “manual” de EasyMock dice:

If you would like to use matchers in a call, you have to specify matchers for all arguments of the method call.

Por tanto, mi código debía quedar como:
EasyMock.expect(mockManager.find(EasyMock.same(LdapObject.class), EasyMock.anyObject())).andReturn(mockLdapObject);

Espero que si alguien tiene el mismo problema, esto le sirva de ayuda.

20 JulTelegrama EasyMock

He estado buscando un tutorial sobre EasyMock y creo que lo he encontrado. STOP. He visto que ha salido hace muy poquito la versión 2.3 pero aún no la he evaluado porque nosotros usamos EasyMock Class Extensions porque así podemos crear mocks a partir de interfaces y no sólo de implementaciones particulares(*). STOP. Para incluir EasyMock Class Extensions en un proyecto usando Maven es muy fácil:
<dependency>   <groupid>org.easymock</groupid>   <artifactid>easymockclassextension</artifactid>   <version>2.2.2</version></dependency>

STOP. Esto último lo he extraído de MVNrepository. STOP.

(*) Ver comentario.