Jasmine

``

  • Testing
  • docs
    • Your_first_suite-your_first_suite

      🧪 Introducción

Jasmine es un framework de testing behavior-driven development (BDD) para JavaScript, diseñado para realizar pruebas unitarias de manera sencilla, legible y sin dependencias externas. Es muy utilizado junto con Karma y otros entornos de integración continua dentro de flujos DevOps.

Su enfoque se basa en describir comportamientos esperados de una aplicación mediante bloques de código expresivos que facilitan la lectura y el mantenimiento de los tests.

⚙️ Características Principales

  • Sintaxis clara y natural basada en describe / it.
  • No requiere DOM ni dependencias externas.
  • Permite mocks, spies y aserciones integradas.
  • Compatible con frameworks como Angular o librerías puras de JavaScript.
  • Integración con entornos de CI/CD y pipelines de DevOps.
  • Soporta pruebas asincrónicas mediante callbacks, Promises y async/await.

🧩 Estructura Básica de un Test

Cada test en Jasmine sigue una estructura jerárquica de descripciones (suites) y especificaciones (specs).

📄 Ejemplo Básico

describe("Calculadora", () => {
	it("debería sumar correctamente", () => {
		const resultado = 2 + 3;
		expect(resultado).toBe(5);
	});

	it("debería restar correctamente", () => {
		const resultado = 10 - 4;
		expect(resultado).toBe(6);
	});
});

`

🧠 Conceptos Clave

  • describe(): agrupa un conjunto de pruebas relacionadas.
  • it(): define un test individual.
  • expect(): define la expectativa o aserción.
  • Matchers: expresan la condición esperada (toBe, toEqual, toContain, toThrow, etc.).

🔍 Matchers Comunes

Matcher Descripción
toBe(value) Compara por identidad (===).
toEqual(value) Compara por valor.
toBeTruthy() / toBeFalsy() Evalúa valores booleanos.
toContain(item) Verifica si un array o string contiene un elemento.
toThrow() Espera una excepción.
toBeCloseTo(number, precision) Compara números flotantes.

🧰 Spies y Mocks

Los spies permiten interceptar y observar llamadas a funciones, sin alterar su comportamiento original (a menos que se especifique).

describe("Spies en Jasmine", () => {
	it("debería espiar una función", () => {
		const usuario = {
			saludar: (nombre) => `Hola ${nombre}`,
		};

		spyOn(usuario, "saludar");
		usuario.saludar("Eduardo");

		expect(usuario.saludar).toHaveBeenCalled();
		expect(usuario.saludar).toHaveBeenCalledWith("Eduardo");
	});
});

Los spies se pueden combinar con métodos como and.returnValue(), and.callFake() o and.throwError() para simular distintos comportamientos.

⏳ Tests Asincrónicos

Jasmine soporta tests con Promises, async/await o callbacks mediante la función done().

it("debería resolver una promesa correctamente", async () => {
	const resultado = await Promise.resolve("ok");
	expect(resultado).toBe("ok");
});
it("debería ejecutar callback async", (done) => {
	setTimeout(() => {
		expect(true).toBeTrue();
		done();
	}, 100);
});

🧮 Configuración y Ejecución

Los tests de Jasmine se pueden ejecutar en navegador o en Node.js.

  • En entornos de frontend, suele integrarse con Karma.
  • En Node.js, se puede instalar globalmente:
npm install --save-dev jasmine
npx jasmine init
npx jasmine

Esto genera un archivo spec/support/jasmine.json con la configuración base.

🧩 Integración con DevOps y CI/CD

  • Puede integrarse en pipelines para ejecutar pruebas automáticamente antes de despliegues.
  • Compatible con entornos como GitHub Actions, Jenkins, GitLab CI o CircleCI.
  • Los reportes pueden exportarse en formatos JUnit, JSON o HTML para análisis continuo.

🔄 Pruebas de Componentes y Servicios (ejemplo con Angular)

En Angular, Jasmine se utiliza junto a TestBed para pruebas unitarias de componentes y servicios.

import { TestBed } from "@angular/core/testing";
import { UsuarioService } from "./usuario.service";

describe("UsuarioService", () => {
	let service: UsuarioService;

	beforeEach(() => {
		TestBed.configureTestingModule({});
		service = TestBed.inject(UsuarioService);
	});

	it("debería crearse correctamente", () => {
		expect(service).toBeTruthy();
	});
});

🌐 Recursos

Jasmine - Conceptos Avanzados y Mejores Prácticas

A continuación se amplían los temas no tratados en profundidad: hooks del ciclo de vida, patrones de testing, configuración avanzada y estrategias de mantenimiento.

🔁 Ciclo de Vida de los Tests

Jasmine ofrece hooks que permiten preparar y limpiar el entorno antes y después de cada test o suite.

describe("Gestor de Usuarios", () => {
	let usuarios;

	beforeAll(() => {
		// Se ejecuta una vez antes de todos los tests
		usuarios = [];
	});

	beforeEach(() => {
		// Se ejecuta antes de cada test
		usuarios.push("nuevo");
	});

	afterEach(() => {
		// Limpieza tras cada test
		usuarios.pop();
	});

	afterAll(() => {
		// Se ejecuta una vez al finalizar todos los tests
		usuarios = null;
	});

	it("debería agregar un usuario", () => {
		expect(usuarios.length).toBe(1);
	});
});

`

Estos hooks son esenciales en pruebas con datos temporales, mocks o configuración global.


🧱 Estructura Escalable de Carpetas

Una estructura modular facilita el mantenimiento de test suites grandes.

/tests
	/specs/
		usuario.spec.js
		auth.spec.js
		utils.spec.js
	/helpers/
		mock-data.js
		custom-matchers.js
	/setup/
		jasmine.json
		setupEnv.js

Recomendaciones:

  • Mantén un archivo helpers/ con funciones o datos reutilizables.
  • Crea matchers personalizados en custom-matchers.js.
  • Centraliza la configuración del entorno de test (variables, spies globales, etc.) en setupEnv.js.

🧩 Creación de Matchers Personalizados

Jasmine permite extender su sistema de aserciones con matchers propios.

beforeEach(() => {
	jasmine.addMatchers({
		toBeEven: () => ({
			compare(actual) {
				const pass = actual % 2 === 0;
				return {
					pass,
					message: pass
						? `Esperaba que ${actual} no fuera par`
						: `Esperaba que ${actual} fuera par`,
				};
			},
		}),
	});
});

it("valida números pares", () => {
	expect(4).toBeEven();
});

Esto resulta útil para dominios específicos (p. ej., validaciones de fechas, estructuras JSON, respuestas HTTP, etc.).


⚡ Optimización de Pruebas Asincrónicas

1. Uso combinado de done y setTimeout

Evitar el bloqueo o falsos positivos.

it("maneja retrasos en respuestas", (done) => {
	fetch("/api/usuario").then((res) => {
		expect(res.status).toBe(200);
		done();
	});
});

2. Promises y async/await

Más legibles y sin necesidad de done().

it("valida respuesta API", async () => {
	const res = await fetch("/api/usuario");
	const data = await res.json();
	expect(data.nombre).toBeDefined();
});

🧮 Patrón “Arrange, Act, Assert” (AAA)

Organiza cada test en tres fases claras:

it("debería calcular la suma correctamente", () => {
	// Arrange
	const a = 5;
	const b = 3;

	// Act
	const resultado = a + b;

	// Assert
	expect(resultado).toBe(8);
});

Ventajas:

  • Claridad en la intención del test.
  • Facilita refactorización y lectura del código.

🧰 Mocks y Spies Combinados con CallThrough

Para observar una función sin anular su comportamiento original:

const servicio = {
	guardar: (item) => `Guardado: ${item}`,
};

spyOn(servicio, "guardar").and.callThrough();

it("ejecuta y espía simultáneamente", () => {
	const resultado = servicio.guardar("dato");
	expect(servicio.guardar).toHaveBeenCalled();
	expect(resultado).toBe("Guardado: dato");
});

🧠 Tests Parametrizados

Puedes iterar sobre múltiples escenarios en una sola suite:

[
	{ input: 2, output: 4 },
	{ input: 3, output: 9 },
	{ input: 4, output: 16 },
].forEach((caso) => {
	it(`debería calcular el cuadrado de ${caso.input}`, () => {
		expect(caso.input ** 2).toBe(caso.output);
	});
});

🔬 Cobertura de Código con Istanbul (nyc)

Para medir qué partes del código están cubiertas por tests:

npm install --save-dev nyc
npx nyc jasmine

Esto genera un reporte en /coverage, útil en pipelines DevOps.


🧩 Integración con Karma + Navegadores

karma.conf.js puede usar Jasmine como framework base:

frameworks: ["jasmine"],
files: ["src/**/*.js", "tests/**/*.spec.js"],
browsers: ["ChromeHeadless"],
reporters: ["progress", "kjhtml"],

Esto permite ejecutar los tests en entornos de navegador reales o headless (sin UI).


🧱 Buenas Prácticas

  • Usa nombres descriptivos: it("debería mostrar error si el email es inválido").
  • Mantén los tests independientes entre sí.
  • Evita lógica compleja dentro de los tests.
  • Reutiliza fixtures o factories para generar datos de prueba.
  • Desactiva funciones de red o base de datos real mediante mocks.

🧠 Estrategias de Escalabilidad

En proyectos grandes:

  • Agrupa suites por dominio funcional.
  • Automatiza la ejecución con watchers (npm run test:watch).
  • Integra en pipelines de despliegue continuo.
  • Configura reportes HTML o JUnit para análisis visual o integración con herramientas como SonarQube.

🌐 Recursos Adicionales

$= dv.current().file.tags.join(“ “)

🧩 Temas Pendientes y Expansión

En esta nota se profundiza en aspectos no cubiertos previamente:
patrones avanzados, integración con frameworks modernos, pruebas de eventos, time mocking, seguridad en tests, depuración y uso en entornos híbridos (browser + Node.js).


⚙️ Integración con Frameworks Modernos

🔹 Angular

Ya se mostró un ejemplo básico, pero en entornos reales se combinan Jasmine + TestBed con spy providers:

import { TestBed } from "@angular/core/testing";
import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing";
import { UsuarioService } from "./usuario.service";

describe("UsuarioService", () => {
	let service: UsuarioService;
	let httpMock: HttpTestingController;

	beforeEach(() => {
		TestBed.configureTestingModule({
			imports: [HttpClientTestingModule],
			providers: [UsuarioService],
		});
		service = TestBed.inject(UsuarioService);
		httpMock = TestBed.inject(HttpTestingController);
	});

	it("debería obtener lista de usuarios", () => {
		const mockUsuarios = [{ nombre: "Eduardo" }];

		service.obtenerUsuarios().subscribe((data) => {
			expect(data).toEqual(mockUsuarios);
		});

		const req = httpMock.expectOne("/api/usuarios");
		expect(req.request.method).toBe("GET");
		req.flush(mockUsuarios);
	});
});

`

Esto demuestra cómo Jasmine puede integrarse profundamente en entornos de testing de componentes y servicios.

🔹 React

Aunque no es el estándar (donde se usa Jest), Jasmine puede funcionar con herramientas como Enzyme o Testing Library si se configura el entorno manualmente.

import React from "react";
import { render, screen } from "@testing-library/react";
import App from "./App";

describe("App Component", () => {
	it("renderiza el título principal", () => {
		render(<App />);
		expect(screen.getByText(/Bienvenido/i)).toBeTruthy();
	});
});

🕒 Time Mocking y Control del Tiempo

Jasmine permite simular y manipular temporizadores, útil para probar delays, intervalos y funciones dependientes de tiempo.

describe("Timers con Jasmine", () => {
	beforeEach(() => {
		jasmine.clock().install();
	});

	afterEach(() => {
		jasmine.clock().uninstall();
	});

	it("controla el tiempo con jasmine.clock()", () => {
		const callback = jasmine.createSpy("callback");

		setTimeout(callback, 1000);
		jasmine.clock().tick(1000);

		expect(callback).toHaveBeenCalled();
	});
});

🔍 Tests de Eventos y DOM (sin navegador real)

En entornos de navegador simulado (por ejemplo con jsdom), Jasmine puede validar eventos sin depender de Karma:

describe("Eventos DOM", () => {
	it("detecta click en botón", () => {
		const button = document.createElement("button");
		let clicked = false;

		button.addEventListener("click", () => (clicked = true));
		button.click();

		expect(clicked).toBeTrue();
	});
});

🧱 Pruebas de Integración entre Módulos

Jasmine también puede orquestar pruebas entre diferentes capas (controladores, servicios, utilidades).

import { obtenerUsuario, formatearUsuario } from "../src/usuario";

describe("Integración de módulos", () => {
	it("debería formatear el nombre del usuario correctamente", async () => {
		const usuario = await obtenerUsuario(1);
		const nombre = formatearUsuario(usuario);
		expect(nombre).toMatch(/^[A-Z]/);
	});
});

Estas pruebas no son unitarias, pero garantizan coherencia entre funciones interdependientes.


🔒 Testing y Seguridad

1. Validación de Inputs

describe("Validación de inputs", () => {
	it("debería rechazar valores inseguros", () => {
		const input = "<script>alert('xss')</script>";
		const resultado = sanitizar(input);
		expect(resultado).not.toContain("<script>");
	});
});

2. Mock de Tokens o Sesiones

beforeEach(() => {
	spyOn(localStorage, "getItem").and.returnValue("fake-token");
});

Evita exponer credenciales o datos reales en los tests.


🧠 Patrón Given-When-Then (Behavior-Driven)

Una extensión del AAA, útil para documentación y claridad:

describe("Login de usuario", () => {
	it("debería autenticar con credenciales válidas", () => {
		// Given
		const credenciales = { user: "admin", pass: "1234" };

		// When
		const resultado = login(credenciales);

		// Then
		expect(resultado).toBeTrue();
	});
});

Esto alinea los tests con el lenguaje del dominio y mejora la comunicación con equipos no técnicos.


🧩 Mocking de Módulos Externos

Si tu código usa APIs o librerías externas, Jasmine puede reemplazarlas temporalmente:

import * as api from "../api";

describe("Mock de módulos externos", () => {
	it("intercepta llamada externa", async () => {
		spyOn(api, "fetchData").and.returnValue(Promise.resolve({ ok: true }));

		const data = await api.fetchData();
		expect(data.ok).toBeTrue();
	});
});

🔎 Depuración y Logs

Durante el desarrollo de tests complejos:

  • Usa console.log() dentro de bloques it() (Jasmine no los bloquea).
  • Usa fit() para ejecutar solo un test.
  • Usa fdescribe() para ejecutar solo una suite.
  • Usa xit() o xdescribe() para ignorar temporalmente tests.
fdescribe("Debugging activo", () => {
	it("solo este test se ejecuta", () => {
		console.log("Test en foco");
		expect(true).toBeTrue();
	});
});

⚡ Integración con Node.js y ESM

Jasmine 5+ soporta módulos ECMAScript nativamente:

npm install jasmine --save-dev
npx jasmine --esm

Y permite import/export directamente:

import { sumar } from "../src/utils.js";

describe("Suma con ESM", () => {
	it("funciona con import/export", () => {
		expect(sumar(2, 2)).toBe(4);
	});
});

📦 Ejecución Paralela y Optimización de Suites

En entornos grandes, Jasmine puede dividir suites en ejecuciones paralelas con herramientas externas (p. ej., karma-parallel o jest-worker):

  • Mejora el rendimiento en pipelines CI.
  • Reduce tiempos de feedback para los desarrolladores.
  • Permite balancear la carga entre procesos o contenedores Docker.

🧮 Integración con DevOps Metrics y Reportes

  • Generación automática de reportes HTML, JSON o JUnit.
  • Integración con dashboards de calidad de código (SonarQube, Codecov, GitHub Actions).
  • Configuración de thresholds de cobertura mínima:
npx nyc --check-coverage --lines 80 --functions 85 --branches 70 jasmine

Esto obliga a mantener una cobertura mínima en el proyecto antes del merge.


🧭 Estrategias de Migración

De Jasmine a Jest (o viceversa)

  • Los matchers y estructura son casi idénticos.
  • Spies → jest.fn()
  • Jasmine → mejor para entornos legacy o Angular.
  • Jest → preferido en ecosistemas modernos y monorepos.

Ejemplo de conversión rápida:

// Jasmine
spyOn(servicio, "getData").and.returnValue(of("ok"));

// Jest
jest.spyOn(servicio, "getData").mockReturnValue(of("ok"));

🧰 Recomendaciones Finales

  • Mantén suites cortas (<50 tests por archivo).
  • Evita mocks excesivos (solo cuando sea necesario).
  • Mide la flakiness (tests intermitentes) en CI.
  • Documenta los matchers personalizados.
  • Usa Jasmine no solo como herramienta de validación, sino como especificación viva del sistema.

🌐 Recursos Relacionados