Web Components y pruebas

Web Components y pruebas

Esta guía organiza las preguntas y respuestas del TXT proporcionado. El contenido se conserva en formato de consulta breve y se agrupa por tema. Los ejemplos son fragmentos ilustrativos; algunos requieren ejecutarse dentro de un navegador o de un proyecto configurado.

WEB COMPONENTS

1. ¿Qué son los Web Components y cuáles son los tres estándares principales que los conforman?

Son tecnologías nativas del navegador para crear elementos HTML reutilizables y encapsulados, sin depender de un framework. Sus tres estándares principales son Custom Elements, Shadow DOM y HTML Templates.

Fragmento ilustrativo:

class MiTarjeta extends HTMLElement {} customElements.define(‘mi-tarjeta’, MiTarjeta);

2. ¿Cuál es la diferencia entre Custom Elements, Shadow DOM y HTML Templates?

Custom Elements permite definir etiquetas y comportamientos propios. Shadow DOM encapsula estructura y estilos; HTML Templates permite declarar fragmentos HTML que se clonan cuando se necesitan.

Fragmento ilustrativo:

const plantilla = document.createElement(‘template’); plantilla.innerHTML = ’ Hola ’; const copia = plantilla.content.cloneNode(true);

3. ¿Cómo se registra un componente personalizado utilizando customElements.define()?

Se crea una clase que normalmente extiende HTMLElement y después se registra con un nombre que contenga un guion. El nombre debe ser único en el registro de elementos personalizados.

Fragmento ilustrativo:

class SaludoApp extends HTMLElement { connectedCallback() { this.textContent = ‘Hola’; } } customElements.define(‘saludo-app’, SaludoApp);

4. ¿Qué diferencia existe entre un componente autónomo y un elemento personalizado integrado?

Un componente autónomo extiende HTMLElement y se usa con una etiqueta propia, por ejemplo . Un elemento integrado extiende una clase HTML nativa y amplía un elemento existente mediante el atributo is; su compatibilidad es más limitada.

Fragmento ilustrativo:

class MiBoton extends HTMLButtonElement {} customElements.define(‘mi-boton’, MiBoton, { extends: ‘button’ }); // Uso: Enviar

5. ¿Cuáles son las ventajas de utilizar Web Components frente a componentes dependientes de React o Angular?

Funcionan con APIs nativas, pueden utilizarse desde distintos frameworks y favorecen la interoperabilidad. También permiten encapsular estilos y comportamiento; a cambio, algunas tareas requieren más código o bibliotecas auxiliares.

Fragmento ilustrativo:

document.querySelector(‘mi-tarjeta’)?.setAttribute(‘titulo’, ‘Producto’);

CICLO DE VIDA DE COMPONENTES NATIVOS

6. ¿Cuáles son los principales métodos del ciclo de vida de un Custom Element?

Los principales son constructor(), connectedCallback(), disconnectedCallback(), adoptedCallback() y attributeChangedCallback(). Cada uno responde a la creación, conexión, desconexión, cambio de documento o modificación de atributos.

Fragmento ilustrativo:

class MiElemento extends HTMLElement { constructor() { super(); } connectedCallback() {} disconnectedCallback() {} adoptedCallback() {} attributeChangedCallback(nombre, anterior, nuevo) {} }

7. ¿Qué sucede cuando se ejecuta connectedCallback() y en qué momento se invoca?

Se ejecuta cuando el elemento personalizado queda conectado al DOM. Es el lugar habitual para iniciar listeners, configurar contenido o comenzar tareas que necesitan que el componente esté insertado.

Fragmento ilustrativo:

connectedCallback() { this.addEventListener(‘click’, this.alHacerClick); }

8. ¿Cuál es la diferencia entre connectedCallback() y disconnectedCallback()?

connectedCallback() se ejecuta al insertar el componente en el documento. disconnectedCallback() se ejecuta al retirarlo, y suele utilizarse para quitar listeners o detener recursos y evitar fugas de memoria.

Fragmento ilustrativo:

connectedCallback() { window.addEventListener(‘resize’, this.actualizar); } disconnectedCallback() { window.removeEventListener(‘resize’, this.actualizar); }

9. ¿Para qué sirve attributeChangedCallback() y cómo se configura observedAttributes?

Permite reaccionar cuando cambia, se añade o se elimina uno de los atributos observados. Se configura con un getter estático observedAttributes que devuelve los nombres de esos atributos.

Fragmento ilustrativo:

static get observedAttributes() { return [‘color’]; } attributeChangedCallback(nombre, viejo, nuevo) { if (nombre === ‘color’) this.style.color = nuevo; }

10. ¿Qué sucede cuando un componente personalizado se elimina del DOM y posteriormente vuelve a insertarse?

Al eliminarse se ejecuta disconnectedCallback(); al insertarse otra vez se ejecuta connectedCallback(). La misma instancia puede reconectarse, por lo que conviene evitar registrar listeners duplicados en cada conexión.

Fragmento ilustrativo:

const elemento = document.querySelector(‘mi-elemento’); elemento.remove(); document.body.append(elemento); // Se conecta de nuevo.

SHADOW DOM

11. ¿Qué es Shadow DOM y cuál es su propósito dentro de los Web Components?

Es un árbol DOM separado que puede adjuntarse a un elemento anfitrión. Su propósito es encapsular estructura y estilos internos para reducir conflictos con el resto de la página.

Fragmento ilustrativo:

const raiz = this.attachShadow({ mode: ‘open’ }); raiz.innerHTML = ’ Contenido encapsulado ’;

12. ¿Cuál es la diferencia entre Light DOM y Shadow DOM?

Light DOM es el contenido normal que se coloca dentro de un elemento desde el documento. Shadow DOM es el árbol interno encapsulado que el componente crea y administra.

Fragmento ilustrativo:

Contenido externo // El span pertenece al Light DOM; la plantilla interna puede vivir en Shadow DOM.

13. ¿Qué diferencia existe entre mode: ‘open’ y mode: ‘closed’ al crear un Shadow Root?

En modo open, JavaScript puede acceder a la raíz mediante elemento.shadowRoot. En modo closed, esa propiedad devuelve null desde fuera, aunque el componente conserva internamente la referencia creada.

Fragmento ilustrativo:

const abierta = elemento.attachShadow({ mode: ‘open’ }); console.log(elemento.shadowRoot === abierta); // true

14. ¿Cómo funciona el aislamiento de estilos CSS dentro de Shadow DOM?

Los selectores CSS normales del documento no atraviesan la frontera del Shadow DOM, y los estilos internos no seleccionan elementos externos. Existen mecanismos controlados como :host, ::slotted() y propiedades CSS personalizadas para definir puntos de integración.

Fragmento ilustrativo:

raiz.innerHTML = <style>:host { display:block } p { color: navy }</style> <p>Estilo interno</p>;

15. ¿Qué son los elementos slot y cómo permiten distribuir contenido externo dentro de un componente?

Un slot es un espacio de inserción dentro de la plantilla Shadow DOM. El contenido Light DOM del consumidor se proyecta en el slot predeterminado o en uno con nombre coincidente.

Fragmento ilustrativo:

raiz.innerHTML = ’ ’; // Uso: Título Cuerpo

EVENTOS PERSONALIZADOS

16. ¿Qué diferencia existe entre un evento nativo y un evento personalizado creado con CustomEvent?

Un evento nativo lo define el navegador, como click o input. CustomEvent permite crear una notificación propia y transportar datos mediante detail.

Fragmento ilustrativo:

const evento = new CustomEvent(‘producto-agregado’, { detail: { id: 12 } });

17. ¿Cómo se crea y dispara un evento personalizado utilizando dispatchEvent()?

Se instancia CustomEvent con el nombre y las opciones necesarias. Después se llama dispatchEvent() sobre el objeto que debe emitirlo; los elementos interesados pueden escucharlo con addEventListener().

Fragmento ilustrativo:

this.dispatchEvent(new CustomEvent(‘guardado’, { bubbles: true, detail: { correcto: true } }));

18. ¿Para qué sirve la propiedad detail de un CustomEvent?

detail contiene información adicional que el emisor quiere entregar a quienes reciben el evento. Puede transportar objetos, identificadores, valores o resultados relacionados con la acción.

Fragmento ilustrativo:

elemento.addEventListener(‘seleccion’, e => { console.log(e.detail.id); }); elemento.dispatchEvent(new CustomEvent(‘seleccion’, { detail: { id: 7 } }));

19. ¿Qué diferencias existen entre las propiedades bubbles, composed y cancelable?

bubbles permite que el evento ascienda por la cadena de ancestros. composed permite que atraviese la frontera del Shadow DOM; cancelable permite que el receptor cancele la acción con preventDefault().

Fragmento ilustrativo:

new CustomEvent(‘accion’, { bubbles: true, composed: true, cancelable: true });

20. ¿Cómo puede comunicarse un componente hijo con un componente padre mediante eventos personalizados cuando existe Shadow DOM?

El hijo emite un CustomEvent y configura bubbles y composed en true para que pueda propagarse fuera de su Shadow Root. El padre escucha el evento en el componente hijo o en un ancestro adecuado.

Fragmento ilustrativo:

this.dispatchEvent(new CustomEvent(‘cambio-valor’, { detail: { valor: 25 }, bubbles: true, composed: true })); // Padre: contenedor.addEventListener(‘cambio-valor’, e => console.log(e.detail.valor));

ARQUITECTURAS BASADAS EN COMPONENTES INDEPENDIENTES

21. ¿Qué significa que una arquitectura esté basada en componentes independientes?

Significa dividir la aplicación en unidades con estructura, comportamiento y responsabilidad definidos. Cada componente expone una interfaz clara y procura depender lo mínimo posible de los detalles internos de otros.

Fragmento ilustrativo:

class Contador extends HTMLElement { connectedCallback() { this.textContent = ‘0’; } }

22. ¿Qué es el bajo acoplamiento y cómo contribuye a la reutilización de componentes?

Es reducir la dependencia que existe entre módulos. Un componente poco acoplado puede cambiarse, probarse o reutilizarse sin obligar a modificar muchos otros componentes.

Fragmento ilustrativo:

function calcularTotal(precio, cantidad) { return precio * cantidad; } // Función independiente: no conoce la interfaz que la utiliza.

23. ¿Cómo se puede establecer comunicación entre dos componentes sin crear dependencias directas?

Se puede utilizar un sistema de eventos, un bus de eventos o un servicio compartido con una interfaz estable. Así, el emisor publica información y el receptor decide si la escucha, sin conocerse directamente.

Fragmento ilustrativo:

const bus = new EventTarget(); bus.addEventListener(‘usuario-cambio’, e => console.log(e.detail)); bus.dispatchEvent(new CustomEvent(‘usuario-cambio’, { detail: { nombre: ‘Ana’ } }));

24. ¿Qué ventajas y desventajas tiene utilizar una arquitectura basada en eventos frente a una arquitectura basada en referencias directas?

Los eventos reducen el acoplamiento y permiten varios receptores, pero el flujo puede ser más difícil de rastrear y depurar. Las referencias directas son sencillas y explícitas, aunque crean dependencias entre los objetos.

Fragmento ilustrativo:

// Eventos: emisor y receptor no se referencian directamente. emisor.dispatchEvent(new CustomEvent(‘listo’, { bubbles: true }));

25. ¿Cómo diseñarías una aplicación frontend para que cada componente tenga una responsabilidad única y pueda probarse independientemente?

Separaría presentación, estado y acceso a datos; cada componente tendría entradas y salidas explícitas. Añadiría pruebas para su comportamiento aislado y pruebas de integración para verificar la comunicación entre componentes.

Fragmento ilustrativo:

function validarCorreo(correo) { return /[1]+@[^@]+.[^@]+$/.test(correo); } // Esta lógica pura puede probarse sin montar la interfaz.

JAVASCRIPT SIN DEPENDER DE UN FRAMEWORK (LÓGICA)

26. ¿Qué diferencias existen entre JavaScript imperativo y declarativo?

El estilo imperativo describe paso a paso cómo realizar una tarea. El declarativo expresa qué resultado se desea, dejando que la operación o abstracción resuelva los pasos.

Fragmento ilustrativo:

// Imperativo const dobles = []; for (const n of [1, 2, 3]) dobles.push(n * 2); // Declarativo const otrosDobles = [1, 2, 3].map(n => n * 2);

27. ¿Cómo funciona el Event Loop y cuál es la diferencia entre Microtasks y Macrotasks?

El Event Loop coordina la ejecución del código, la pila de llamadas y las colas de tareas. Al vaciar la pila, procesa microtasks (por ejemplo, callbacks de Promise) antes de pasar a la siguiente macrotask, como setTimeout.

Fragmento ilustrativo:

console.log(‘A’); setTimeout(() => console.log(‘B’), 0); Promise.resolve().then(() => console.log(‘C’)); console.log(‘D’); // Orden: A, D, C, B.

28. ¿Qué son las closures y cómo pueden utilizarse para encapsular información?

Una closure ocurre cuando una función conserva acceso al ámbito léxico donde fue creada, incluso después de que ese ámbito haya terminado. Permite mantener datos privados y exponer operaciones controladas.

Fragmento ilustrativo:

function crearContador() { let valor = 0; return () => ++valor; } const contar = crearContador(); contar(); // 1

29. ¿Cuál es la diferencia entre var, let y const, considerando su alcance y hoisting?

var tiene alcance de función y puede redeclararse; let y const tienen alcance de bloque y no permiten redeclaración en el mismo bloque. Las tres declaraciones se procesan antes de ejecutar el código, pero let y const permanecen en la zona temporal muerta hasta su inicialización.

Fragmento ilustrativo:

var a = 1; let b = 2; const c = 3; // No se puede reasignar c.

30. ¿Cómo implementarías un sistema de comunicación entre componentes utilizando JavaScript nativo sin recurrir a un framework?

Utilizaría EventTarget como bus central y CustomEvent para publicar mensajes con datos en detail. Los componentes se suscriben al evento que necesitan y eliminan sus listeners cuando dejan de estar activos.

Fragmento ilustrativo:

const canal = new EventTarget(); canal.addEventListener(‘mensaje’, e => console.log(e.detail)); canal.dispatchEvent(new CustomEvent(‘mensaje’, { detail: ‘Hola componentes’ }));

PROGRAMACIÓN ORIENTADA A OBJETOS (POO I)

31. ¿Cuáles son los cuatro pilares de la programación orientada a objetos?

Son encapsulación, abstracción, herencia y polimorfismo. En conjunto permiten proteger el estado, ocultar detalles, reutilizar comportamiento y tratar objetos distintos mediante una interfaz común.

Fragmento ilustrativo:

class Animal { hablar() { return ‘Sonido’; } } class Perro extends Animal { hablar() { return ‘Guau’; } }

32. ¿Qué diferencia existe entre una clase y un objeto?

Una clase define una estructura y comportamiento que pueden compartir sus instancias. Un objeto es una instancia concreta que contiene valores propios y puede ejecutar los métodos definidos por su clase.

Fragmento ilustrativo:

class Usuario { constructor(nombre) { this.nombre = nombre; } } const usuario = new Usuario(‘Luis’);

33. ¿Qué es la encapsulación y cómo se implementa en JavaScript?

Consiste en controlar el acceso al estado interno de un objeto y exponer operaciones seguras. En JavaScript puede implementarse con campos privados que comienzan con # y métodos públicos.

Fragmento ilustrativo:

class Cuenta { #saldo = 0; depositar(cantidad) { if (cantidad > 0) this.#saldo += cantidad; } consultar() { return this.#saldo; } }

34. ¿Qué es la herencia y cómo funciona mediante extends y super()?

La herencia permite que una clase derive de otra y reutilice o amplíe sus métodos. extends establece la relación; super() llama al constructor de la clase padre y debe ejecutarse antes de usar this en una subclase.

Fragmento ilustrativo:

class Persona { constructor(nombre) { this.nombre = nombre; } } class Empleado extends Persona { constructor(nombre, puesto) { super(nombre); this.puesto = puesto; } }

35. ¿Qué es el polimorfismo y cómo podrías implementarlo utilizando clases de JavaScript?

Es la capacidad de objetos distintos de responder a una misma operación con comportamientos diferentes. En JavaScript puede lograrse sobrescribiendo un método en distintas subclases.

Fragmento ilustrativo:

class Figura { area() { return 0; } } class Cuadrado extends Figura { constructor(lado) { super(); this.lado = lado; } area() { return this.lado ** 2; } }

HTML5

36. ¿Qué es HTML semántico y cuál es la diferencia entre utilizar div y elementos como header, main, section y article?

HTML semántico utiliza etiquetas cuyo nombre comunica la función del contenido. div es un contenedor genérico; header, main, section y article aportan significado estructural que ayuda a navegadores, buscadores y tecnologías de asistencia.

Fragmento ilustrativo:

Encabezado Noticia

37. ¿Qué diferencia existe entre los atributos id, class, data-* y name?

id identifica un elemento de forma única dentro del documento; class agrupa elementos para estilos o scripts. data-* almacena datos personalizados en el HTML y name identifica campos, especialmente al enviar formularios.

38. ¿Para qué sirven los atributos required, pattern, min, max y type en los formularios HTML5?

type define el tipo de control o dato; required obliga a proporcionar un valor. pattern establece una expresión regular y min/max limitan valores numéricos, fechas u otros tipos compatibles.

39. ¿Qué son los atributos async y defer en la carga de scripts y en qué se diferencian?

Ambos permiten descargar scripts sin detener el análisis del HTML. async ejecuta el script tan pronto como termina de descargarse, sin garantizar orden; defer espera a que termine el análisis y conserva el orden entre scripts diferidos.

40. ¿Qué son los atributos ARIA y cómo contribuyen a la accesibilidad de una aplicación web?

ARIA añade información semántica a tecnologías de asistencia cuando el HTML nativo no expresa suficientemente el rol, estado o nombre de un control. No sustituye al HTML semántico ni agrega comportamiento de teclado automáticamente.

Fragmento ilustrativo:

Menú

JAVASCRIPT (JS)

41. ¿Cuál es la diferencia entre == y ===?

== compara valores permitiendo conversiones de tipo en ciertos casos. === compara sin conversión implícita y exige que coincidan tanto el tipo como el valor; suele ser la opción más segura.

Fragmento ilustrativo:

‘5’ == 5; // true ‘5’ === 5; // false

42. ¿Qué diferencias existen entre null, undefined y NaN?

undefined indica normalmente que un valor no ha sido asignado; null representa una ausencia intencional. NaN significa “Not-a-Number” y aparece en operaciones numéricas inválidas; su comprobación fiable es Number.isNaN().

Fragmento ilustrativo:

let dato; console.log(dato); // undefined console.log(null); // ausencia intencional console.log(Number.isNaN(0 / 0)); // true

43. ¿Qué son las promesas y cómo funcionan then(), catch() y finally()?

Una Promise representa el resultado futuro de una operación asíncrona: pendiente, cumplida o rechazada. then() procesa el éxito, catch() gestiona errores y finally() se ejecuta al terminar, haya éxito o fallo.

Fragmento ilustrativo:

fetch(‘/api/datos’) .then(r => r.json()) .then(datos => console.log(datos)) .catch(error => console.error(error)) .finally(() => console.log(‘Terminado’));

44. ¿Cuál es la diferencia entre map(), filter(), reduce() y forEach()?

map() transforma cada elemento y devuelve un nuevo arreglo; filter() selecciona elementos. reduce() acumula un resultado y forEach() ejecuta una función por elemento sin crear un arreglo de resultados.

Fragmento ilustrativo:

const nums = [1, 2, 3]; nums.map(n => n * 2); // [2, 4, 6] nums.filter(n => n > 1); // [2, 3] nums.reduce((a, n) => a + n, 0); // 6 nums.forEach(n => console.log(n));

45. ¿Qué diferencia existe entre una función tradicional, una función flecha y el comportamiento de this en ambas?

Una función tradicional tiene su propio this según cómo se invoque y puede usarse como constructor. Una función flecha no crea su propio this: hereda el del contexto léxico y no puede utilizarse con new.

Fragmento ilustrativo:

const objeto = { valor: 10, normal() { return this.valor; }, flecha: () => this.valor };

CSS3

46. ¿Cómo funciona la especificidad CSS y qué prioridad tienen los selectores de ID, clase y elemento?

La especificidad decide qué regla prevalece cuando varias reglas compiten por una propiedad. En términos generales, ID tiene más peso que clase/atributo/pseudoclase, y estos tienen más que los selectores de elemento; también intervienen origen, importancia y orden de cascada.

Fragmento ilustrativo:

p { color: green; } .texto { color: blue; } #principal { color: red; } /* Gana entre estas reglas */

47. ¿Qué diferencia existe entre relative, absolute, fixed y sticky en la propiedad position?

relative conserva su espacio original y se desplaza respecto a su posición normal. absolute sale del flujo y se posiciona respecto a su ancestro posicionado; fixed se fija al viewport y sticky alterna entre flujo normal y posición fija según el desplazamiento.

Fragmento ilustrativo:

.relativo { position: relative; } .absoluto { position: absolute; top: 0; } .fijo { position: fixed; bottom: 0; } .pegajoso { position: sticky; top: 0; }

48. ¿Cuál es la diferencia entre Flexbox y CSS Grid y cuándo utilizarías cada uno?

Flexbox organiza elementos principalmente en una dimensión: fila o columna. Grid organiza filas y columnas en dos dimensiones; usaría Flexbox para alineaciones y distribuciones lineales, y Grid para estructuras de página o cuadrículas.

Fragmento ilustrativo:

.fila { display: flex; gap: 1rem; } .malla { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }

49. ¿Qué es el Box Model y cómo afecta box-sizing: border-box al cálculo de dimensiones?

El modelo de caja incluye contenido, padding, borde y margen. Con content-box, width/height se aplican al contenido; con border-box, incluyen contenido, padding y borde, haciendo más predecible el tamaño exterior.

Fragmento ilustrativo:

- { box-sizing: border-box; } .caja { width: 200px; padding: 20px; border: 2px solid; }

50. ¿Cómo funciona la herencia de estilos y qué mecanismos permiten aislar CSS entre componentes?

Algunas propiedades, como color y font-family, suelen heredarse; otras, como margin y border, no. Para aislar estilos pueden utilizarse Shadow DOM, convenciones de nombres, módulos CSS o ámbitos controlados por herramientas.

Fragmento ilustrativo:

:host { display: block; } :host p { color: #333; }

LIT / LITELEMENT

51. ¿Qué es LitElement y qué ventajas ofrece frente a implementar un Web Component utilizando únicamente JavaScript nativo?

LitElement es la clase base de Lit para crear Web Components con plantillas declarativas y propiedades reactivas. Reduce código repetitivo y actualiza la vista cuando cambian propiedades, manteniendo los estándares de Custom Elements y Shadow DOM.

Fragmento ilustrativo:

import { LitElement, html } from ‘lit’; class Saludo extends LitElement { render() { return html<p>Hola</p>; } } customElements.define(‘mi-saludo’, Saludo);

52. ¿Cómo se definen las propiedades reactivas utilizando static properties y qué sucede cuando cambia una propiedad?

Se declaran en static properties con su tipo y opciones. Cuando una propiedad reactiva cambia, Lit programa una actualización y vuelve a evaluar la plantilla para reflejar el nuevo estado.

Fragmento ilustrativo:

static properties = { nombre: { type: String } }; constructor() { super(); this.nombre = ‘Ana’; }

53. ¿Cuál es la función de render() y cómo funciona la interpolación de valores utilizando html?

render() devuelve la plantilla que representa la interfaz del componente. La etiqueta html permite insertar expresiones JavaScript con ${…}, actualizando los nodos necesarios sin reconstruir manualmente todo el DOM.

Fragmento ilustrativo:

render() { return html<h1>Hola, ${this.nombre}</h1>; }

54. ¿Qué diferencias existen entre willUpdate(), updated() y firstUpdated()?

willUpdate() se ejecuta antes de actualizar el DOM y sirve para preparar valores derivados. updated() se ejecuta después de cada actualización; firstUpdated() se ejecuta una sola vez después de la primera actualización.

Fragmento ilustrativo:

willUpdate() { /* preparar datos / } firstUpdated() { / configuración inicial del DOM / } updated() { / reaccionar a actualizaciones */ }

55. ¿Cómo implementarías la comunicación entre dos componentes Lit utilizando propiedades, eventos personalizados y Shadow DOM?

El padre pasa datos al hijo mediante propiedades; el hijo informa cambios emitiendo eventos personalizados. Si el evento debe salir del Shadow DOM, se configura con bubbles y composed en true; Shadow DOM mantiene encapsulada la implementación visual.

Fragmento ilustrativo:

this.dispatchEvent(new CustomEvent(‘seleccionado’, { detail: { id: this.itemId }, bubbles: true, composed: true })); // En el padre: @seleccionado=${this.alSeleccionar}

PRUEBAS UNITARIAS CON JEST

56. ¿Qué es Jest y cuáles son sus principales ventajas para realizar pruebas unitarias en proyectos JavaScript?

Jest es un entorno de pruebas para JavaScript que incluye ejecución de tests, aserciones, mocks y medición de cobertura. Su configuración suele ser sencilla y permite probar funciones, módulos y código asíncrono.

Fragmento ilustrativo:

test(‘suma dos números’, () => { expect(2 + 3).toBe(5); });

57. ¿Cuál es la diferencia entre toBe(), toEqual() y toStrictEqual()?

toBe() comprueba igualdad estricta, normalmente identidad o el mismo valor primitivo. toEqual() compara profundamente propiedades; toStrictEqual() también distingue casos como propiedades undefined y diferencias de estructura más estrictamente.

Fragmento ilustrativo:

expect(2 + 2).toBe(4); expect({ a: 1 }).toEqual({ a: 1 }); expect([1, undefined]).toStrictEqual([1, undefined]);

58. ¿Qué son los mocks y cómo se utilizan jest.fn() y jest.mock()?

Los mocks sustituyen funciones o módulos reales para controlar su comportamiento y observar llamadas. jest.fn() crea una función simulada; jest.mock() reemplaza un módulo para aislar la unidad que se está probando.

Fragmento ilustrativo:

const guardar = jest.fn(); guardar(‘dato’); expect(guardar).toHaveBeenCalledWith(‘dato’);

59. ¿Cómo se realizan pruebas de código asíncrono utilizando async/await, promesas y Jest?

La prueba debe esperar el resultado asíncrono para que Jest no la marque como terminada antes de tiempo. Se puede declarar el test como async y usar await, o devolver directamente una promesa.

Fragmento ilustrativo:

test(‘obtiene un dato’, async () => { const resultado = await Promise.resolve(8); expect(resultado).toBe(8); });

60. ¿Qué son los hooks beforeEach(), afterEach(), beforeAll() y afterAll() y cuándo utilizarías cada uno?

beforeEach() y afterEach() se ejecutan antes y después de cada prueba; beforeAll() y afterAll() se ejecutan una vez por conjunto de pruebas. Se usan para preparar datos, crear recursos y limpiar el entorno.

Fragmento ilustrativo:

beforeAll(() => iniciarServidor()); beforeEach(() => limpiarDatos()); afterEach(() => restaurarMocks()); afterAll(() => cerrarServidor()); 18. PRUEBAS UNITARIAS CON MOCHA.JS

61. ¿Qué es Mocha.js y cuál es su función dentro del ecosistema de pruebas de JavaScript?

Mocha es un framework de pruebas que organiza y ejecuta pruebas JavaScript en Node.js o en el navegador. Permite pruebas síncronas y asíncronas; normalmente se combina con una biblioteca de aserciones como Chai o el módulo assert.

Fragmento ilustrativo:

const assert = require(‘node:assert/strict’); describe(‘suma’, () => { it(‘suma dos valores’, () => assert.equal(2 + 2, 4)); });

62. ¿Cuál es la diferencia entre describe(), it() y context()?

describe() agrupa pruebas relacionadas; it() define un caso de prueba concreto. context() es un alias de describe() que algunas convenciones usan para describir un contexto o condición.

Fragmento ilustrativo:

describe(‘Usuario’, () => { context(‘cuando es válido’, () => { it(‘tiene correo’, () => {}); }); });

63. ¿Cómo se utiliza assert o Chai para comprobar que una función devuelve el resultado esperado?

Se ejecuta la función y se compara el resultado real con el esperado mediante una aserción. assert forma parte de Node.js; Chai ofrece estilos como expect y should.

Fragmento ilustrativo:

const assert = require(‘node:assert/strict’); assert.equal(sumar(2, 3), 5); // Chai: expect(sumar(2, 3)).to.equal(5);

64. ¿Cómo se realizan pruebas de funciones asíncronas utilizando Mocha y qué diferencias existen entre done() y async/await?

Mocha espera una promesa si el test devuelve una Promise o utiliza async/await. done() es un callback que debe invocarse al finalizar; no debe mezclarse con el retorno de una promesa en el mismo test.

Fragmento ilustrativo:

it(‘resuelve una promesa’, async () => { const valor = await Promise.resolve(10); assert.equal(valor, 10); }); // Alternativa callback: it(‘callback’, done => operacion(done));

65. ¿Qué son los hooks before(), beforeEach(), after() y afterEach() y cuál es su utilidad?

before() y after() se ejecutan una vez antes y después de las pruebas del bloque. beforeEach() y afterEach() se ejecutan alrededor de cada prueba, y sirven para preparar y limpiar el estado.

Fragmento ilustrativo:

before(() => conectarBaseDatos()); beforeEach(() => limpiarRegistros()); afterEach(() => restaurarEstado()); after(() => desconectarBaseDatos());

FIN DE LA GUÍA

© Arturo Juárez Monroy