Adrianistán

El blog de Adrián Arroyo


Usar AVA para tests en una API hecha en Node.js y Express

- Adrián Arroyo Calle

Testear nuestras aplicaciones es algo fundamental si queremos garantizar un mínimo de calidad. En este breve post, explicaré como he usado AVA para crear tests para mis APIs: tanto unitarios como de integración con AVA.



AVA es una herramienta de testing, que nos permite describir los tests de forma muy sencilla. De todas las herramientas que he probado, AVA es muy preferida. Es muy sencilla, ejecuta los tests en paralelo y permite escribir código en ES6 (usa Babel por debajo). Además tiene bastante soporte siendo el framework de testing usando por muchos proyectos ya.

Instalamos AVA de la forma típica:
npm install ava --save-dev

A continuación creamos un fichero cuya terminación sea .test.js, por ejemplo, suma.test.js. El lugar da igual.

Una opción de diseño es poner los test unitarios al lado de las funciones en sí, otra opción es crear una carpeta para todos los tests, ya que los de integración van a ir casi siempre ahí. Para ejecutar los tests, simplemente:
ava

El interior de suma.test.js debe importar la función test de AVA y las funciones que quiera probar.

Los tests se definen como una llamada a la función test con la descripción del test y a continuación un callback (asíncrono si queremos) con el objeto que controla los tests (llamado t normalmente). Veamos un ejemplo simple:
import test from "ava";
import {suma} from "./operaciones";

test("Suma",t => {
t.is(suma(1,2),3);
});

El objeto t soporta múltiples operaciones, siendo is la más básica. Is pide que sus dos argumentos sean iguales entre sí, como Assert.Equal de xUnit.

Veamos que más soporta Ava.

  • t.pass(): Continúa el test (sigue)

  • t.fail(): Falla el test (no sigue)

  • t.truthy(val): Continúa el test si val es verdaderoso (usando la lógica de JavaScript) o falla el test

  • t.true(val): Continúa el test si val es verdadero (más estricto que el anterior) o falla.

  • t.is(val1,val2): Continúa el test si val1 y val2 son iguales (superficialmente) o falla.

  • t.deepEqual(val1,val2): Continúa el test si val1 y val2 son iguales (profundamente) o falla.

  • t.throws(funcion): Ejecuta la función especificada esperando que lance una excepción. Si no lo hace, falla el test. Se puede especificar el tipo de excepción que esperamos en el segundo argumento.

  • t.notThrows(funcion): Exactamente lo contrario que la anterior.


Y algunas más, pero estas son las esenciales.
import test from "ava";

function sum(a,b){
return a+b;
}

function login(username,password){
if(username === null || password === null){
throw new Error("Missing username or password");
}
}

test("Test example: Sum",t => {
t.is(sum(1,2),3);
});

test("Login fail username null", t => {
t.throws(()=>{
login(null,"123456");
});
});
test("Login fail password null", t => {
t.throws(()=>{
login("username",null);
});
});

También podemos definir funciones que se ejecuten antes y después de nuestros tests, y una sola vez o con cada test. Podemos usar test.before, test.beforeEach, test.after y test.afterEach en vez de test. Por ejemplo, si tienes una base de datos que necesita inicialización, puedes definir ese código en test.before y la limpieza en test.after.
import test from "ava";
import db from "../db";

test.before(async () => {
// Iniciar el ORM Sequelize
await db.sync();
});

 

Con esto ya podemos hacer tests unitarios, pero no podemos probar la aplicación web al 100%. Entra en acción supertest que nos permitirá tener un servidor Express simulado para que los tests puedan probar la aplicación al completo.

Supertest


Instalamos supertest
npm install supertest --save-dev

En el fichero de test necesitamos crear un objeto de tipo aplicación de Express. Este objeto puede ser el mismo que usas en tu aplicación real o ser una versión simplificada con lo que quieras probar.
import test from "ava";
import request from "supertest";
import auth from "http-auth";
import express from "express";

function setup(){
const app = express();

let basic = auth.basic({
realm: "Upload Password"
}, function (username, password, callback) {
callback(username === "admin" && password === "123456");
});

app.get("/upload",auth.connect(basic),function(req,res){
res.sendFile("upload.html");
});
return app;
}

test("Página upload requiere autenticación HTTP Basic", async t => {
let res = await request(setup())
.get("/upload")
.send();
t.is(res.status,401);
t.true(res.header["www-authenticate"] !== undefined);
});

Aquí la función de test es asíncrona, AVA es compatible con ambos tipos. Supertest es muy completo y permite probar APIs enteras con su sencilla interfaz que junto con AVA, se convierte en algo casi obligatorio para una aplicación que vaya a producción.

Comentarios

Añadir comentario

Todos los comentarios están sujetos a moderación