Armando un mock de un servidor HTTP

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.

Compartir
  • acandal

    Muy buena la nota! Me sorprendió lo sencillo de instanciar el servidor web de jetty

  • Invitado

    Muy interesante el post.<br /><br />Estoy intentando "adaptarlo" para poder usar los test unitarios en los filtros que tengo definidos, pero por ahora no lo he conseguido... ¿alguna idea?

Deja tus comentarios

Post comment as a guest

0

El nuevo Dos Ideas.

Nuevo logo, nuevo buscador, nueva portada, podcast mensual... ¡y muchas novedades más!

Más novedades en Dos Ideas

Los Comentarios.

Hay una gran contradicción de lo que es PaaS y el listado de los PaaS actuales que ponen al final, A...
invitado
Todo lo que dice en este post es la verdad, me ha llegado al corazon esos consejos y los aplicare de...
This is very interesting, You're a very skilled blogger. I have joined your feed and look forward to...
lupita gares
Muy bueno, disculpen alguien sabe por qué en mis reglas no puedo ver atributos de una subclase? es d...
Lo que Pensamos es lo que somos, cada circunstancia que pasamos es producto de algún pensamiento Neg...

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