Mockito (y II)
Introducción
En el tutorial de introducción
a mockito vimos ciertos ejemplo de cómo realizar ciertas operaciones con
mockito. En este tutorial vamos a ver un ejemplo más cercano a la vida real que
nos ilustre la potencia de utilizar mock objects en nuestras pruebas.
El ejemplo trata de un manager de sistemas remotos, que accede a dos capas
de datos diferentes: una de autenticación de usuarios y otra donde busca los
datos de los sistemas remotos con losa que trabajar (pensad que es un ejemplo y
el código no es funcional, pero eso es lo interesante).
Para el ejemplo sólo se han definido las interfaces de los DAO de
autenticación y de acceso a datos de sistemas, y sin embargo podremos hacer
pruebas de nuestro manager, ¡gracias a mockito!, potente, ¿no creeis?.
Código de ejemplo
Os podéis descargar el proyecto completo de eclipse de SystemManager (contruido con Maven).
Veamos el código de la clase a probar:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
public class SystemManager { private final AuthDAO authDao; private final GenericDAO dao; public SystemManager(AuthDAO authDao, GenericDAO dao) { super(); this.authDao = authDao; this.dao = dao; } public void startRemoteSystem(String userId, String remoteId) throws SystemManagerException { final User auth = authDao.getAuthData(userId); try { Collection<Object> remote = dao.getSomeData(auth, "where id=" + remoteId); //operacion start de remote //remote.start(); } catch (OperationNotSupportedException e) { throw new SystemManagerException(e); } } public void stopRemoteSystem(String userId, String remoteId) throws SystemManagerException { final User auth = authDao.getAuthData(userId); try { Collection<Object> remote = dao.getSomeData(auth, "where id=" + remoteId); //operacion start de remote //remote.stop(); } catch (OperationNotSupportedException e) { throw new SystemManagerException(e); } } public void addRemoteSystem(String userId, Object remote) throws SystemManagerException { final User auth = authDao.getAuthData(userId); boolean isAdded = false; try { isAdded = dao.updateSomeData(auth, remote); } catch (OperationNotSupportedException e) { throw new SystemManagerException(e); } if (!isAdded) { throw new SystemManagerException("cannot add remote"); } } public void deleteRemoteSystem(String userId, String remoteId) throws SystemManagerException { //generamos un error.. sin querer siempre tenemos un usuario valido //final User auth = authDao.getAuthData(userId); final User auth = new User("1", "German", "Jimenez", "Madrid", new ArrayList<Object>()); boolean isDeleted = true; try { //otro error: no seteamos isDeleted dao.deleteSomeData(auth, remoteId); } catch (OperationNotSupportedException e) { throw new SystemManagerException(e); } if (!isDeleted) { throw new SystemManagerException("cannot delete remote: does remote exists?"); } } } |
los dao no son mas que interfaces:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
public interface GenericDAO { /** * Obtiene datos filtrados de la capa subyacente * * @param auth informacion de autenticacion para roles * @param filter filtro para la informacion. Si filter es null se devuelven todos los datos * @return coleccion de objetos obtenidos * @throws OperationNotSupportedException si no se puede realizar la operacion por * permisos */ public Collection<Object> getSomeData(User auth, Object filter) throws OperationNotSupportedException; /** * Actualiza un dato de la capa subyacente, o lo añade si no existiera * * @param auth informacion de autenticacion para roles * @param data dato a actualizar o añadir * @return true si todo fue bien, false si no se pudo actualizar * @throws OperationNotSupportedException si no se puede realizar la operacion por * */ public boolean updateSomeData(User auth, Object data) throws OperationNotSupportedException; /** * Elimina un dato de la capa subyacente * * @param auth informacion de autenticacion para roles * @param data dato a borrar * @return true si todo fue bien, false si no se pudo eliminar * @throws OperationNotSupportedException si no se puede realizar la operacion por * */ public boolean deleteSomeData(User auth, String id) throws OperationNotSupportedException; } public interface AuthDAO { /** * Obtiene la informacion de autenticacion de la capa subyacente * * @param userId id del usuario * @return informacion de roles de usuario, o null si no se encontrara */ public User getAuthData(String userId); } |
No nos queda ver más que los test, que son autoexplicativos, utilizando
mockito y junit 4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
package com.autentia.mockitoExample; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import static org.mockito.Matchers.*; import java.util.ArrayList; import javax.naming.OperationNotSupportedException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.runners.MockitoJUnit44Runner; import com.autentia.mockitoExample.dao.AuthDAO; import com.autentia.mockitoExample.dao.GenericDAO; import com.autentia.mockitoExample.dao.User; //runner de mockito que detecta las anotaciones @RunWith(MockitoJUnit44Runner.class) public class SystemManagerTest { // generamos un mock con anotaciones @Mock private AuthDAO mockAuthDao; // generamos un mock mediante el metodo mock private GenericDAO mockGenericDao = mock(GenericDAO.class); // variable inOrder de mockito para comprobar llamadas en orden private InOrder ordered; // el manager a testear private SystemManager manager; // Un usuario valido, para pruebas private static final User validUser = new User("1", "German", "Jimenez", "Madrid", new ArrayList<Object>()); // Un usuario invalido, para pruebas private static final User invalidUser = new User("2", "Autentia", "Autentia", "San Fernando de Henares", null); // id valido de sistema private static final String validId = "12345"; // id invalido de sistema private static final String invalidId = "54321"; /** * Inicializamos cada una de las pruebas * * @throws Exception */ @Before public void setUp() throws Exception { // instanciamos el manager con losmock creados manager = new SystemManager(mockAuthDao, mockGenericDao); // stubbing del mock del DAO de autenticacion. // solo hacemos stubbiong delos metodos copn datos que nos interesan // no toiene sentido simular TODA la funcionalidad del objecto del que // hacemos mocks when(mockAuthDao.getAuthData(validUser.getId())).thenReturn(validUser); when(mockAuthDao.getAuthData(invalidUser.getId())).thenReturn(null); // stubbing del mock del DAO de acceso a los datos de sistemas when(mockGenericDao.getSomeData(validUser, "where id=" + validId)) .thenReturn(new ArrayList<Object>()); when(mockGenericDao.getSomeData(validUser, "where id=" + invalidId)) .thenThrow(new OperationNotSupportedException()); // usamos argument matchers para el filtro pues nos da igual when(mockGenericDao.getSomeData((User) eq(null), anyObject())) .thenThrow(new OperationNotSupportedException()); when(mockGenericDao.deleteSomeData(validUser, "where id=" + validId)) .thenReturn(true); when(mockGenericDao.deleteSomeData(validUser, "where id=" + invalidId)) .thenReturn(true); when(mockGenericDao.deleteSomeData((User) eq(null), anyString())) .thenReturn(true); // primero debe ejecutarse la llamada al dao de autenticacion, y despues // el de acceso a datos del sistema (la validaciones del orden en cada // prueba) ordered = inOrder(mockAuthDao, mockGenericDao); } /** * Prueba que un sistema deberia inicializarse con un usuario y sistema * validos * * @throws Exception */ @Test public void testShouldStartRemoteSystemWithValidUserAndSystem() throws Exception { // llamada al api a probar manager.startRemoteSystem(validUser.getId(), validId); // vemos si se ejecutan las llamadas pertinentes alos dao, y en el orden // correcto ordered.verify(mockAuthDao).getAuthData(validUser.getId()); ordered.verify(mockGenericDao).getSomeData(validUser, "where id=" + validId); } /** * Prueba que un sistema no se puede iniciar debido a un usuario invalido * * @throws Exception */ @Test(expected = SystemManagerException.class) public void testShouldFailStartRemoteSystemWithInvalidUser() throws Exception { try { // llamada al api a probar manager.startRemoteSystem(invalidUser.getId(), validId); fail("cannot work with invalid user"); } catch (SystemManagerException e) { // vemos si se ejecutan las llamadas pertinentes alos dao, y en el // orden correcto ordered.verify(mockAuthDao).getAuthData(invalidUser.getId()); ordered.verify(mockGenericDao).getSomeData((User) eq(null), anyObject()); throw e; } } /** * Prueba que un sistema no se puede iniciar debido a un sistema inexistente * * @throws Exception */ @Test(expected = SystemManagerException.class) public void testShouldFailStartRemoteSystemWithValidUserAndInvalidSystem() throws Exception { try { // llamada al api a probar manager.startRemoteSystem(validUser.getId(), invalidId); fail("cannot work with invalid system"); } catch (SystemManagerException e) { // vemos si se ejecutan las llamadas pertinentes alos dao, y en el // orden correcto ordered.verify(mockAuthDao).getAuthData(validUser.getId()); ordered.verify(mockGenericDao).getSomeData(validUser, "where id=" + invalidId); throw e; } } /** * Prueba que un sistema se elimina correctamente * * @throws Exception */ @Test public void testShouldDeleteRemoteSystemWithValidUserAndSystem() throws Exception { // llamada al api a probar manager.deleteRemoteSystem(validUser.getId(), validId); // vemos si se ejecutan las llamadas pertinentes alos dao, y en el orden // correcto ordered.verify(mockAuthDao).getAuthData(validUser.getId()); ordered.verify(mockGenericDao).getSomeData(validUser, "where id=" + validId); } /** * Prueba que un sistema no se puede borrar debido a un usuario invalido * * @throws Exception */ @Test(expected = SystemManagerException.class) public void testShouldFailDeleteRemoteSystemWithInvalidUser() throws Exception { try { // llamada al api a probar manager.deleteRemoteSystem(invalidUser.getId(), validId); fail("cannot work with invalid user, but method doesn't fails"); } catch (SystemManagerException e) { // vemos si se ejecutan las llamadas pertinentes a los dao, y en el // orden correcto ordered.verify(mockAuthDao).getAuthData(invalidUser.getId()); ordered.verify(mockGenericDao).getSomeData((User) eq(null), anyObject()); throw e; } } /** * Prueba que un sistema no se puede borrar debido a un sistema invalido * * @throws Exception */ @Test(expected = SystemManagerException.class) public void testShouldDeleteStartRemoteSystemWithValidUserAndInvalidSystem() throws Exception { try { // llamada al api a probar manager.deleteRemoteSystem(validUser.getId(), invalidId); fail("cannot work with invalid system, but method doesn't fails"); } catch (SystemManagerException e) { // vemos si se ejecutan las llamadas pertinentes alos dao, y en el // orden correcto ordered.verify(mockAuthDao).getAuthData(validUser.getId()); ordered.verify(mockGenericDao).getSomeData(validUser, "where id=" + invalidId); throw e; } } } |
Conclusiones
Podéis observar lo sencillo que es realizar pruebas usando mocks. De esta
forma nos centramos en probar el manager, olvidando los posibles errores, o los
requisitos de configuración de las capas subyacentes. Lo más importante es ver
que no estamos probando nada del dao, sólo verificando que el manager realiza
las llamadas correctas.
Ya sabéis que podéis contactar con nosotros en Autentia si tenéis alguna duda o
necesitáis de nuestros servicios.