Framework de testing en Deno

Typescript es perfecto para detectar pequeños errores en nuestro código que podrían pasar desapercibidos en lenguajes dinámicos como JavaScript. Sin embargo, no es una solución mágica libre de errores, ya que no puede identificar problemas relacionados con la lógica de nuestro código. Aquí es donde entran los tests y el desarrollo basado en tests, o test-driven development (TDD).

Históricamente, los tests en JavaScript han sido caóticos, obligándonos a elegir entre diferentes frameworks y lidiar con configuraciones complicadas. Con Deno, este proceso se simplifica gracias a su framework de pruebas integrado, que facilita enormemente la creación de tests y elimina las complicaciones innecesarias de configuración.

#Nuestro primer test en Deno

Supongamos que tenemos la siguiente función llamada multiply, que aparenta funcionar correctamente, pero en realidad tiene un pequeño error de lógica matemática.

main.ts
export function multiply(a: number, b: number) {
return a ** b;
}

Veamos cómo identificar este problema utilizando tests.

En Deno, los tests pueden ubicarse en cualquier lugar, pero es una buena práctica crear un archivo dedicado para probar cada funcionalidad de nuestro código. Dentro del archivo de test, usamos la función test del namespace de Deno. Esta función toma como primer argumento un nombre descriptivo para el test y, como segundo argumento, una función. En esta función definimos las aserciones para garantizar que nuestro código funcione como esperamos.

main_test.ts
import { multiply } from "./main.ts";
import { assertEquals } from "@std/assert";
 
Deno.test(function multiplyTest() {
assertEquals(multiply(2, 2), 4);
});

La librería @std de Deno nos proporciona varias funciones para realizar aserciones, como assertEquals, que toma dos argumentos: el primero es el resultado de nuestra función (en este caso, multiplicar dos por dos) y el segundo es el valor esperado (cuatro). Así de sencillo es escribir un test básico.

Si ejecutamos el comando deno test main_test.ts, veremos que el test pasa, pero no es muy exhaustivo. Agreguemos otra aserción para comprobar que multiplicar dos por tres devuelve seis.

main_test.ts
import { multiply } from "./main.ts";
import { assertEquals } from "@std/assert";
 
Deno.test(function multiplyTest() {
assertEquals(multiply(2, 2), 4);
+ assertEquals(multiply(2, 3), 6);
});

Volvemos a ejecutar el test y veremos que ahora falla.

Los tests nos ayudan a entender mejor nuestro código. En este caso, el test devolvió 9 en lugar de 6. Si revisamos nuestra función multiply, notaremos que estamos realizando una exponenciación en lugar de una multiplicación. Para solucionarlo, reemplazamos el doble asterisco por un solo asterisco y volvemos a ejecutar el test. Esta vez, el test pasa.

main.ts
export function multiply(a: number, b: number) {
- return a ** b;
+ return a * b;
}

Otro beneficio de los tests es que, si en el futuro refactorizamos esta función, nuestra suite de pruebas evitará que introduzcamos regresiones que rompan nuestro código.

#Utilizando expect en vez de aserciones

Si eres un desarrollador experimentado en JavaScript, probablemente estés familiarizado con Jest, un framework de pruebas muy popular en los últimos años. Aunque Deno tiene sus propias funciones de aserciones, también podemos utilizar expect, que forma parte de la librería @std de Deno y es compatible con Jest.

Por ejemplo, podemos reescribir el test anterior utilizando expect.

main_test.ts
import { multiply } from "./main.ts";
import { expect } from "jsr:@std/expect";
 
Deno.test("multiply test", () => {
- assertEquals(multiply(2, 3), 6);
+ expect(multiply(2, 3)).toBe(6);
});

La función expect incluye varios matchers que podemos encadenar, lo cual resulta familiar si hemos usado Jest antes.

#Añadiendo un texto descriptivo

Otra característica importante es que, en lugar de proporcionar el nombre de la función como primer argumento del test, podemos pasar una descripción en texto y luego incluir la función como segundo argumento.

main_test.ts
import { expect } from "jsr:@std/expect";
 
-Deno.test(function multiplyTest() {
+Deno.test("multiply test", () => {
expect(multiply(2, 3)).toBe(6);
});

#Tests asíncronos

La función test también es asíncrona, permitiendo crear promesas y simular datos que se resolverán posteriormente, como una llamada API con fetch. En este caso, la función test esperará a que la promesa se resuelva antes de realizar la aserción. Esto es ideal para operaciones asíncronas.

main_test.ts
Deno.test("mock API call", async () => {
const mockApi = () => Promise.resolve("mock data");
const result = await mockApi();
assertEquals(result, "mock data");
});

#Obteniendo el contexto de un test

En una aplicación del mundo real, a menudo trabajamos con bases de datos que requieren pasos de configuración y limpieza entre pruebas. Deno facilita este proceso al proporcionar un contexto de prueba dentro de la función callback. Podemos implementar los pasos de configuración, como simular una base de datos con un map, y luego escribir pruebas individuales utilizando await t.step. Cada paso se ejecuta secuencialmente, garantizando que las pruebas funcionen correctamente y evitando problemas de mutación de datos.

Por ejemplo, primero verificamos que la base de datos se creó correctamente usando assertExists. Luego, insertamos un usuario, comprobamos que el tamaño de la base de datos sea mayor a cero y que el valor insertado cumpla con un patrón de expresión regular.

main_test.ts
Deno.test("database lib", async (t) => {
const db = new Map();
 
await t.step("db exists", () => {
assertExists(db);
});
 
await t.step("insert user", () => {
db.set("user", "oliver");
 
assertGreater(db.size, 0);
assertMatch(db.get("user"), /oliver/);
assertNotMatch(db.get("user"), /Clementina/);
});
});

En el paso final, implementamos los pasos de limpieza, como eliminar los datos almacenados en la base de datos.

#Reportes de cobertura

Otra funcionalidad útil de Deno es la generación de reportes de cobertura de código, que muestran qué porcentaje de nuestro código base está cubierto por pruebas. Aunque personalmente no utilizo esta funcionalidad en proyectos personales, muchos equipos de trabajo valoran estos reportes. Deno facilita su generación sin necesidad de depender de librerías adicionales.