Probando módulos de NancyFx

En el artículo anterior de la serie de NancyFx hablábamos de diferentes tipos de respuesta así como el uso de plantillas en la plataforma. En este artículo veremos cómo crear pruebas unitarias para asegurarnos que nuestros módulos funcionan correctamente.

Creando nuestro primer test

Desde la documentación de Nancy se recomienda usar un proyecto diferente (es decir, un ensamblado diferente) para nuestros tests, ya que los módulos se descubren de manera automática por el «bootstrapper». Una vez creado nuestro proyecto, necesitamos agregar las siguientes referencias, via nuget o de manera manual:

  • NUnit (o XUnit, o el framework que deseemos)
  • Nancy
  • Nancy.Testing

Una vez tenemos las referencias, podemos crear nuestro primer test (que tiene el original nombre de RoutesTest.cs). Dicho test estará dividido en tres partes:

  • Arrange: Toda la preparación necesaria para poder llevar a cabo el test
  • Act: La ejecución de nuestro test
  • Assert: La fase de comprobación de los resultados
[Test ()]
public void TestPut ()
{
        //Arrange
	var bootstrapper = new DefaultNancyBootstrapper();
	var browser = new Browser(bootstrapper);

        //Act
	var result = browser.Put("/routes", with => {
		with.HttpRequest();
	});

        //Assert
	Assert.AreEqual (HttpStatusCode.OK, result.StatusCode);
	Assert.AreEqual ("Response with Put\n", result.Body.AsString());
}

Qué hace el test?

  • Arrange: Para este test el primer paso es crear nuestro Bootstrapper que descubrirá los módulos existentes, seguido de un Browser con el que simularemos las llamadas a nuestros módulos.

  • Act: Una vez tenemos el browser, el siguiente paso es generar la peticion, en este caso Put, a una ruta especificada.

  • Assert: Con el resultado de la ejecución, comprobamos el estado de la respuesta (en este caso 200 OK) y que el texto corresponda con lo que necesitamos, que es «Response with Put\n».

Una vez tenemos las referencias, recordemos qué contenía el módulo que queríamos probar:

public class Routes : NancyModule
{
	public Routes ()
	{
		Get["/routes"] = _ => "Response with GET\n";
		Post["/routes"] = _ => "Response with POST\n";
		Put["/routes"] = _ => "Response with Put\n";
                ...
	}
}

Podemos identificar claramente una línea donde el verbo PUT devuelve «Response with Put\n», con lo cual nuestro test es correcto

Probando una ruta errónea

Al igual que podemos probar rutas correctas, los test son igual de válidos para comprobar que el acceso a una ruta no autorizada o no implementada se controla correctamente, como sucede a continuación:

[Test ()]
public void TestNotFound ()
{
	//Arrange
	var bootstrapper = new DefaultNancyBootstrapper();
	var browser = new Browser(bootstrapper);

	//Act
	var result = browser.Delete("/routes", with => {
		with.HttpRequest();
	});

	//Assert
	Assert.AreEqual (HttpStatusCode.MethodNotAllowed, result.StatusCode);
}

En este caso, la ruta no está implementada, con lo cual el resultado será un error de tipo «Method Not Allowed».

Conclusiones

Con pocas líneas de código podemos comenzar a probar nuestros módulos de NancyFx, podemos probar la respuesta de los mismos, así como códigos de error. El sistema de test permite, adicionalmente, pasar parámetros (simulando el uso de un formulario) o navegar a través del DOM del HTML devuelto, si usamos un ViewEngine.

Tipos de respuesta y plantillas en NancyFx

En los artículos anteriores de la serie, hacíamos un breve repaso a cómo crear módulos y rutas con NancyFx, en este veremos cómo responder a las peticiones usando texto plano, JSON o ficheros, así como usar un viewengine que nos permita, a través de plantillas, usar elementos generados por el servidor en documentos HTML.

Presentando al Content-Type

El Content-type es un mensaje que forma parte de los encabezados HTTP y especifica el tipo de recurso que soliticamos y que se devuelve en una respuesta para que el navegador lo pueda procesar. De esa manera, un content-type de tipo text/html se procesará en el cliente como un fichero HTML a renderizar, el tipo application/octet-stream se referirá habitualmente a un fichero binario, y así para ficheros de audio, vídeo u otros.

Nancy permite especificar el tipo de contenido que estamos devolviendo, de esta manera podemos rescatar el ejemplo anterior donde especificábamos el código de retorno, y modificarlo para que devuelva un contenido con un tipo mime específico:

public class ResponseTypes : NancyModule
{
	public ResponseTypes ()
	{
		Get["/responses/text"] = _ => {
			return ((Response)"<h1>Texto plano</h1>\n").
				WithContentType("text/plain");
		};

		Get["/responses/html"] = _ => {
			return ((Response)"<h1>Texto HTML</h1>\n").
				WithContentType("text/html");
		};

		Get["/responses/binary"] = _ => {
			return ((Response)"<h1>Texto en un fichero</h1>\n").
				WithContentType("application/octet-stream");
		};
	}
}

En este caso, la primera respuesta, aunque espefiquemos código HTML, mostrará el texto plano, en la segunda hemos especificado que se trata de html, con lo cual la respuesta se renderizará por pantalla. En la tercera respuesta, hemos establecido el tipo a binario, con lo cual la acción a realizar será la de descargar el contenido a un fichero en disco.

JSON

Javascript Object Notation, o JSON, es un viejo conocido de este blog, ya que lo hemos usado para acceder a Azure Mobile Services, a Twitter, y a otros. Es una representación ligera de datos que tiene un formato similar a este:

{
    "firstName": "John",
    "lastName": "Smith",
    "age": 25,
    "address": {
        "streetAddress": "21 2nd Street",
        "city": "New York",
        "state": "NY",
        "postalCode": 10021
    },
    "phoneNumbers": [
        {
            "type": "home",
            "number": "212 555-1234"
        },
        {
            "type": "fax",
            "number": "646 555-4567"
        }
    ]
}

Una de las cosas que nos permite NancyFx, es codificar en formato Json un objeto (o un conjunto de ellos) para devolverlos al cliente. Veamos un ejemplo:

public class Person
{
	public string Name {
		get;
		set;
	}
	public string Surname {
		get;
		set;
	}
}public class JsonResponse : NancyModule
{
	public JsonResponse ()
	{
		Get["/json/single"] = _ => Response.AsJson(
			new Person()
			{
				Name = "Roberto",
				Surname = "Luis"
			}
		);
	}
}

En este ejemplo, creamos un objeto, y usando Response.AsJson codificamos su contenido en formato Json y lo devolvemos. La respuesta, si la viésemos en un navegador, tendría este aspecto:

{
    "Name": "Roberto",
    "Surname": "Luis"
}

Si examinamos cuidadosamente la cabecera de la respuesta (usando curl por ejemplo), veremos además el valor del campo Content-Type:

Content-Type: application/json; charset=utf8

Lo que ha ocurrido es que Nancy automáticamente ha gestionado las cabeceras por nosotros. Contamos además con métodos como AsXml, AsImage o AsFile para devolver nuestros objetos en otros formatos.

ViewEngines

Un view engine, o motor de vistas, es un componente que nos permite combinar plantillas HTML con llamadas al servidor, evitandonos el engorro de trabajar directamente con html dentro del código, y posiblitando una separación de responsabilidades. Nancy incluye uno por defecto, llamado «Super Simple View Engine», que permite tener una plantilla de este estilo:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"> <title>External template</title>
  </head>
  <body>
    <h1>Hello, @Model.Name!</h1>
    <ul>
    @Each.Model.Children
    <li>@Current</li>
      @EndEach
    </ul>
  </body>
</html>

Los campos @Model.Name y @Model.Children, nos pueden recordar a Razor, el motor de vistas de ASP.net MVC3, mientras el uso de @Each.Model nos pueden recordar a las plantillas erb de ruby. Estos elementos nos permiten agregar datos de manera dinámica a un documento HTML.

La variable @Model, a la que hemos accedido desde la vista, se define desde el controlador, y puede ser un objeto perteneciente a una clase, o como se muestra en el ejemplo, un objeto anónimo que contenga los datos que necesitamos:

public class ViewTemplates : Nancy.NancyModule
{
	public ViewTemplates ()
	{
		Get ["/ViewEngines/WithData"] = _ => {
			var model = new { 
				Name = "John Smith", 
				Children = new []{"Mike", "Bob", "James"}
			};
			return View["ViewEngines/TemplateWithData.html", model];
		};
	}
}

Finalmente, se devuelve la vista usando la ruta relativa de la misma, y el objeto que servirá de modelo.

Resumen

En este artículo se ha visto cómo podemos personalizar el tipo de respuesta, ya sea texto, binario, o JSON, así como usar un motor de vistas para separar la vista del controlador. Con este artículo terminamos esta serie de introducción a NancyFx, si deseas consultar más información, aquí están los artículos anteriores de la serie:

Enlaces adicionales

Módulos y rutas en NancyFx

Este artículo continúa la serie de introducción a Nancy, un framework para aplicaciones web escrito en C#. En el artículo anterior (Primer contacto con NancyFX) hacíamos una breve introducción a la sintaxis, y veíamos un ejemplo muy básico de rutas. En este veremos con un poco más de detalle qué es un módulo, cómo funciona el sistema de rutas, cómo definirlas, y qué tipo de respuestas podemos dar.

Módulos

using System;

namespace NancyFxSinatra
{
	public class SampleModule : Nancy.NancyModule
	{
		public SampleModule()
		{
			Get["/"] = _ => "Hello World!";
		}
	}
}

En el artículo anterior mostrábamos el siguiente código. Las rutas están definidas en el constructor de un módulo, que es a su vez la pieza clave que define el comportamiento de nuestra aplicación.

Un módulo hereda de la clase NancyModule y se puede definir en cualquier parte de la aplicación. No es necesario declararlos explícitamente ya que Nancy se encargará de buscarlos por nosotros. Esta búsqueda se realiza al iniciar la aplicación, quedando en caché una vez encontrados.

Esto nos permitiría, en una hipotética aplicación de gestión tener nuestro ProductModule, que gestionara productos, y nuestro CategoriesModule que gestionara categorías. Los módulos pueden encontrarse en diferentes carpetas, o incluso en diferentes ensamblados.

Rutas

Todos los caminos conducen a Roma

Una ruta se compone por:

Método + Patrón + Acción.

Método

El método es el verbo HTTP por el que accedemos a un recurso. A continuación podemos ver un ejemplo de acceso a diferentes recursos con el mismo patrón.

using System;

namespace NancyFxSinatra
{
	public class Routes : Nancy.NancyModule
	{
		public Routes ()
		{
			Get["/routes"] = _ => "Respuesta usando GET\n";
			Post["/routes"] = _ => "Respuesta usando POST\n";
		}
	}
}

Para probarlo en este caso usaremos la línea de comandos y la utilidad curl, que permite hacer peticiones a una pagina, y devuelve el resultado. Se encuentra disponible para Mac y Linux de manera nativa, y para Windows lo podemos obtener de su página oficial.

Si queremos probar la llamada por GET, usamos el comando:

curl -X GET http://127.0.0.1:8080/routes

Y la salida que obtendremos será:

Respuesta usando GET

Por otra parte, si queremos probar la llamada por POST, el comando será:

curl -X POST http://127.0.0.1:8080/routes

Con la correspondiente salida:

Respuesta usando POST

Así podemos probar el resto de métodos HTTP soportados, que son GET, POST, PUT, DELETE, PATCH, HEAD y OPTIONS.

Patrón

El patrón, por su parte, nos permite detectar una url específica, así como capturar elementos. Podemos destacar los siguiente patrones:

  • «/users»: Detecta la coincidencia exacta.
  • «/users/{id}»: Al caso anterior, captura además cualquier elemento que se pase a continuación. En el caso de «/users/roberto», id tendría el valor de roberto.
  • «/users/{id}/{format?}»: Además de lo anterior, detecta una ruta que puede contener, o no ese valor. En el caso de «/users/roberto/json», format tendría el valor de json. Si por lo contrario usamos una ruta de «/users/roberto» también será capturada, ya que el parámetro es opcional.
  • «users/{cur*}»: A diferencia del anterior, solamente detecta y captura entradas que comiencen por cur, por lo tanto «/users/roberto» no sería detectado, pero «/users/cur1239» sí.

Podemos encadenar patrones como hemos visto antes para crear rutas más complejas, y usar expresiones regulares para una captura más específica.

Acción

Las acciones son el contenido de nuestra llamada, es decir, el resultado de la misma, es de tipo Response, sin embargo, según la respuesta del método se puede devolver:

  • entero: Se interpreta entonces como código de estado HTTP (ejemplo, 404).
  • HttpStatusCode: Código de respuesta incluido en Nancy, que corresponde también a estados HTTP.
  • texto: Interpretado como el cuerpo de la respuesta.

Para demostrarlo, veamos 4 tipos de respuesta, las 3 primeras son las que hemos mostrado, mientras que la última devuelve un error y además de una respuesta en el cuerpo.

Get ["/actions/ok"] = _ => 202;
Get ["/actions/auth"] = _ => HttpStatusCode.Forbidden;
Get ["/actions/text"] = _ => "Hola Mundo";Get["/actions/error"] = _ => {
    Response res =  ((Response)"Error con mensaje\n");
    res.StatusCode = HttpStatusCode.InternalServerError;
    return res;
};

Usando curl (con la opción -v para que muestre también las cabeceras) podemos comprobar que el resultado de la petición corresponde con los códigos que hemos mostrado.

Resumen

En este artículo hemos visto qué son los módulos, cómo, usando comandos HTTP, podemos especificar la manera para acceder a un recurso. Hemos visto también, cómo capturar rutas específicas o que cumplan con algún criterio, y finalmente, hemos devuelto mensajes HTTP usando números y enumerados. En el próximo artículo veremos cómo usar ViewEngines y plantillas HTML para contenido.

Más información: