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:
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
los dao no son mas que interfaces:
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 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
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());
// 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());
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.