Ejemplo con Mockito

0
59733

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:

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 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 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());

  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:

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.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad