JettyLos Mock Objects son "objetos falsos" que sustituyen a otro en funcionalidad. Se utilizan ampliamente en las pruebas unitarias para poder simular el comportamiento de las dependencias. Por suerte hay varios frameworks que nos permiten crear mocks de objetos facilmente. Pero, ¿qué pasa cuando la dependencia es un servidor HTTP externo? Supongamos que tenemos un objeto que realiza alguna petición HTTP a un servidor web externo, que brinda algún tipo de servicio. ¿Cómo podemos simular el comportamiento de este servidor?

En este artículo vamos a ver cómo armar una prueba unitaria con JUnit que utilice Jetty para simular el comportamiento de un servidor HTTP.

Jetty, el servidor web pequeñito

Jetty es un proyecto de software libre que brinda un servidor HTTP, un cliente HTTP y un contenedor de Servlets. Por su flexibilidad y bajos requerimientos Jetty se utiliza embebido en muchos otros proyectos, herramientas y dispositivos.

Entonces, vamos a utilizar Jetty para simular a nuestro servidor web remoto, de manera de poder manipular las respuestas según nuestras necesidades. La prueba unitaria JUnit se encargará de iniciar y detener Jetty para cada uno de los métodos de test. Además, cada test le dará a Jetty el comportamiento que se desea para esa prueba específica.

Un primer vistazo al API de Jetty

Antes de empezar, vamos a ver cómo iniciar y detener a Jetty programáticamente. Primero necesitamos los JAR de Jetty dentro de nuestro classpath:

  • servlet-api-2.5-6.x.jar
  • jetty-util-6.x.jar
  • jetty-6.x.jar

Iniciar y detener Jetty es muy facil. En la siguientes líneas iniciamos el servidor en el puerto 8080:

 
Server server = new Server(8080);
server.start();
...
server.stop();
 

Ahora bien, necesitamos asociar un gestionador de peticiones, es decir, código que se ejecutará cuando llegue una petición al servidor. Hay varias formas de hacerlo, pero la más simple es creando un Handler. El Handler se invocará cada vez que alguien haga una petición a nuestro servidor. Entonces, el ejemplo completo para crear un Handler, asociarlo al servidor e iniciarlo quedaría algo así:

 
Handler handler = new AbstractHandler()
{
    public void handle(String target, 
        HttpServletRequest request, 
        HttpServletResponse response, 
        int dispatch)
        throws IOException, ServletException
    {
        response.setContentType("text/html");
        response.setStatus(HttpServletResponse.SC_OK);
        response.getWriter().println("Hola, mundo");
        ((Request)request).setHandled(true);
    }
};
 
Server server = new Server(8080);
server.setHandler(handler);
server.start();

El Handler anterior devuelve el texto "Hola, mundo" a cualquier petición que se le realice.

Jetty con JUnit

Entonces, ya sabemos iniciar un servidor Jetty programáticamente, asociarle un Handler y detenerlo. El paso final será asociar todo esto a una prueba JUnit, de manera que al iniciar el test se inicalice el servidor, y cada método de prueba pueda cambiar el Handler y poner el comportamiento que desea para el componente bajo test.

Sabiendo como iniciar Jetty resulta muy facil. En el ejemplo siguiente vamos a iniciar Jetty dentro de una prueba JUnit, y cada método de test se encargará de crear un Handler con distinto comportamiento. En una implementación real después de asignar el Handler deberíamos ejercitar nuestro componente bajo test. En este ejemplo, por cuestiones didácticas, usamos Apache HttpClient para verificar el comportamiento esperado.

 
public class HttpMockTest {
 
    private static final int PUERTO_HTTP = 3210;
    private static final String URL_SERVICIO = "http://localhost:" + PUERTO_HTTP;
 
    /** El server http que servirá para hacer un mock del servicio real */
    private Server httpServer;
 
 
    @Before
    public void setUp() throws Exception {
        //iniciamos el servidor mock
        httpServer = new Server(PUERTO_HTTP);
        httpServer.start();
 
    }
 
    @After
    public void tearDown() throws Exception {
        httpServer.stop();
    }
 
 
    /** Esta prueba simula una respuesta OK (200) del servidor.
     *  Para esto se crea un handler que devuelve un codigo de estado 200
     *  junto con una respuesta.
     *
     *  Luego usando HttpClient se verifica que el servidor devuelva el valor
     *  deseado a nuestra petición. Esta invocación con HttpClient debería ser
     *  reemplazada por el componente de negocio que realiza la invocación.
     */
    @Test
    public void respuestaOk() throws IOException {
        Handler handler = new AbstractHandler() {
 
            public void handle(String target, 
                    HttpServletRequest request, 
                    HttpServletResponse response, 
                    int dispatch)
                    throws IOException, ServletException {
                response.setContentType("text/html");
                response.setStatus(HttpServletResponse.SC_OK);
                response.getWriter().print("Hola");
                ((Request) request).setHandled(true);
            }
        };
        httpServer.setHandler(handler);
 
        //A continuación ejercitaríamos al componente bajo test (por ej, un DAO)
        //En este ejemplo usamos la librería Apache HttpClient para verificar el
        //el comportamiento esperado.
        HttpClient client = new HttpClient();
        HttpMethod method = new GetMethod(URL_SERVICIO);
        int status = client.executeMethod(method);
        String respuesta = method.getResponseBodyAsString();
 
        assertEquals(HttpServletResponse.SC_OK, status);
        assertEquals("Hola", respuesta);
    }
 
 
    /**
     * Esta prueba simula una petición no encontrada (404) en el servidor.
     * Para esto creamos un handler que devuelve un código de estado 404.
     */
    @Test
    public void respuestaNoEncontrado() throws IOException {
        Handler handler = new AbstractHandler() {
 
            public void handle(String target, 
                    HttpServletRequest request, 
                    HttpServletResponse response, 
                    int dispatch)
                    throws IOException, ServletException {
                response.setContentType("text/html");
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                ((Request) request).setHandled(true);
            }
        };
        httpServer.setHandler(handler);
 
        HttpClient client = new HttpClient();
        HttpMethod method = new GetMethod(URL_SERVICIO);
        int status = client.executeMethod(method);
 
        assertEquals(HttpServletResponse.SC_NOT_FOUND, status);
    }
 
 
    /**
     * Esta prueba simula una petición que genera un error en el servidor (500).
     * Para esto creamos un handler que devuelve un código de estado 500.
     */
    @Test
    public void respuestoErrorEnServidor() throws IOException {
        Handler handler = new AbstractHandler() {
 
            public void handle(String target, 
                    HttpServletRequest request, 
                    HttpServletResponse response, 
                    int dispatch)
                    throws IOException, ServletException {
                response.setContentType("text/html");
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                ((Request) request).setHandled(true);
            }
        };
        httpServer.setHandler(handler);
 
        HttpClient client = new HttpClient();
        HttpMethod method = new GetMethod(URL_SERVICIO);
        int status = client.executeMethod(method);
 
        assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, status);
    }
 
}
 

Descarga del ejemploDescargar el ejemplo

Pueden descargar el proyecto de ejemplo que incluye todas las librerías necesarias para ejecutar la prueba.

Inspiración.

"Si tú tienes una manzana y yo tengo una manzana e intercambiamos las manzanas, entonces tanto tú como yo seguiremos teniendo una manzana cada uno. Pero si tú tienes una idea y yo tengo una idea, e intercambiamos las ideas, entonces ambos tendremos dos ideas"

Bernard Shaw