JavaScript
Tutorial JavaScript: Pruebas unitarias
Fundamentos de pruebas unitarias: cómo probar funciones con Jest
Las pruebas unitarias son la base de una estrategia de testing efectiva en cualquier proyecto JavaScript. Estas pruebas se centran en verificar el comportamiento de unidades individuales de código, generalmente funciones, de manera aislada. Jest es un framework de testing desarrollado por Facebook que se ha convertido en el estándar de facto para realizar pruebas en aplicaciones JavaScript modernas.
Configuración inicial de Jest
Para comenzar a trabajar con Jest, primero necesitamos configurar nuestro entorno de desarrollo.
Primero inicializa un proyecto:
Después, instala Jest como dependencia de desarrollo:
npm install --save-dev jest
Configuración del script de test en el archivo package.json
:
{
"scripts": {
"test": "jest"
}
}
Con esta configuración básica, ya podemos ejecutar nuestras pruebas mediante el comando npm test
.
Estructura de archivos para pruebas
Jest sigue una convención de nomenclatura para identificar automáticamente los archivos de prueba. Existen varias opciones:
- Archivos con extensión
.test.js
o.spec.js
- Archivos dentro de un directorio
__tests__
La estructura recomendada es mantener los archivos de prueba cerca del código que prueban:
├── src/
│ ├── math.js
│ └── math.test.js
Anatomía de una prueba unitaria
De forma muy básica, podemos crear una función hola mundo y exportarla:
Después podemos crear un test unitario para probar esa función:
Y con el comando npm test
podemos verificar que el test se ejecuta y funciona bien:
Una prueba unitaria en Jest tiene la siguiente estructura básica:
// Importamos la función a probar
const { sum } = require('./math');
// Bloque de prueba
test('adds 1 + 2 to equal 3', () => {
// Preparación (Arrange)
const a = 1;
const b = 2;
// Ejecución (Act)
const result = sum(a, b);
// Verificación (Assert)
expect(result).toBe(3);
});
Este patrón se conoce como AAA (Arrange-Act-Assert) o Preparación-Ejecución-Verificación:
- Preparación: configuramos los datos de entrada
- Ejecución: llamamos a la función que queremos probar
- Verificación: comprobamos que el resultado es el esperado
Funciones principales de Jest
Jest proporciona varias funciones globales para estructurar nuestras pruebas:
test()
(o su aliasit()
): define un caso de prueba individualdescribe()
: agrupa casos de prueba relacionadosbeforeEach()
,afterEach()
: ejecuta código antes/después de cada pruebabeforeAll()
,afterAll()
: ejecuta código antes/después de todas las pruebas
Veamos un ejemplo más completo:
// math.js
function sum(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
module.exports = { sum, multiply };
// math.test.js
const { sum, multiply } = require('./math');
describe('Math operations', () => {
// Se ejecuta antes de cada prueba
beforeEach(() => {
console.log('Running a test');
});
test('adds two numbers correctly', () => {
expect(sum(2, 3)).toBe(5);
});
test('multiplies two numbers correctly', () => {
expect(multiply(2, 3)).toBe(6);
});
});
Probando funciones con diferentes tipos de entradas
Es importante probar nuestras funciones con diferentes tipos de entradas, incluyendo casos límite y valores inesperados:
// calculator.js
function divide(a, b) {
if (b === 0) {
throw new Error('Cannot divide by zero');
}
return a / b;
}
module.exports = { divide };
// calculator.test.js
const { divide } = require('./calculator');
describe('divide function', () => {
test('divides two positive numbers', () => {
expect(divide(10, 2)).toBe(5);
});
test('divides a negative and a positive number', () => {
expect(divide(-10, 2)).toBe(-5);
});
test('throws an error when dividing by zero', () => {
// Verificamos que la función lanza una excepción
expect(() => divide(10, 0)).toThrow('Cannot divide by zero');
});
});
Mocking de dependencias
Una de las características más potentes de Jest es la capacidad de simular dependencias (mocking). Esto nos permite aislar completamente la unidad que estamos probando:
// userService.js
const api = require('./api');
function getFullUserInfo(userId) {
const user = api.getUser(userId);
const posts = api.getUserPosts(userId);
return {
...user,
posts
};
}
module.exports = { getFullUserInfo };
// userService.test.js
const { getFullUserInfo } = require('./userService');
const api = require('./api');
// Simulamos el módulo api
jest.mock('./api');
test('getFullUserInfo returns user with posts', () => {
// Configuramos el comportamiento simulado
api.getUser.mockReturnValue({ id: 1, name: 'John' });
api.getUserPosts.mockReturnValue(['Post 1', 'Post 2']);
const result = getFullUserInfo(1);
// Verificamos el resultado
expect(result).toEqual({
id: 1,
name: 'John',
posts: ['Post 1', 'Post 2']
});
// Verificamos que las funciones simuladas fueron llamadas correctamente
expect(api.getUser).toHaveBeenCalledWith(1);
expect(api.getUserPosts).toHaveBeenCalledWith(1);
});
Pruebas de funciones asíncronas
Jest facilita la prueba de código asíncrono mediante varias técnicas:
// dataService.js
async function fetchData() {
return new Promise(resolve => {
setTimeout(() => {
resolve({ name: 'John', age: 30 });
}, 100);
});
}
module.exports = { fetchData };
// dataService.test.js
const { fetchData } = require('./dataService');
// Usando async/await
test('fetchData returns user data', async () => {
const data = await fetchData();
expect(data).toEqual({ name: 'John', age: 30 });
});
// Usando el parámetro done
test('fetchData returns user data (callback style)', done => {
fetchData().then(data => {
expect(data).toEqual({ name: 'John', age: 30 });
done();
});
});
// Usando resolves/rejects
test('fetchData promise resolves to user data', () => {
return expect(fetchData()).resolves.toEqual({ name: 'John', age: 30 });
});
Cobertura de código
Jest incluye herramientas para medir la cobertura de código de nuestras pruebas, lo que nos ayuda a identificar partes del código que no están siendo probadas:
npm test -- --coverage
Esto generará un informe detallado que muestra qué porcentaje de nuestro código está cubierto por pruebas, desglosado por archivos, funciones, líneas y ramas.
También podemos configurar umbrales de cobertura en el archivo jest.config.js
:
module.exports = {
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
Con esta configuración, Jest fallará si la cobertura cae por debajo del 80% en cualquiera de las métricas.
Buenas prácticas para pruebas unitarias
- Mantén las pruebas pequeñas y enfocadas: cada prueba debe verificar una sola funcionalidad.
- Usa nombres descriptivos: el nombre de la prueba debe describir claramente lo que se está probando.
- Sigue el patrón AAA: Arrange-Act-Assert para mantener la estructura clara.
- Evita dependencias entre pruebas: cada prueba debe poder ejecutarse de forma independiente.
- Prueba tanto casos positivos como negativos: verifica que tu código maneja correctamente los errores.
- Mantén las pruebas rápidas: las pruebas lentas desalientan su ejecución frecuente.
Aserciones básicas con Jest: Validar resultados
Las aserciones son el corazón de cualquier prueba unitaria, ya que permiten verificar que el código se comporta exactamente como esperamos. Jest proporciona una API de aserciones intuitiva y expresiva que facilita la validación de resultados de manera clara y precisa.
Fundamentos de las aserciones en Jest
En Jest, las aserciones se construyen utilizando la función expect()
combinada con matchers (comparadores). Esta sintaxis crea expresiones legibles que se asemejan al lenguaje natural:
expect(valorActual).toMatcher(valorEsperado);
El flujo básico consiste en:
- Pasar el valor a verificar a la función
expect()
- Encadenar un matcher que define la condición que debe cumplirse
Matchers para comparaciones básicas
Jest ofrece una amplia variedad de matchers para diferentes tipos de comparaciones:
- toBe(): Compara identidad estricta (equivalente a
===
)
function sum(a, b) {
return a + b;
}
test('2 + 2 equals 4', () => {
expect(sum(2, 2)).toBe(4);
});
- toEqual(): Compara valores de manera recursiva (útil para objetos y arrays)
test('object assignment', () => {
const data = {name: 'John'};
data['age'] = 30;
expect(data).toEqual({name: 'John', age: 30});
});
Es importante entender la diferencia entre **toBe()**
y **toEqual()**
:
test('demonstrating toBe vs toEqual', () => {
const obj1 = {a: 1};
const obj2 = {a: 1};
// Falla: compara referencias, no contenido
// expect(obj1).toBe(obj2);
// Pasa: compara contenido de manera profunda
expect(obj1).toEqual(obj2);
});
Matchers para valores booleanos y nulos
Para verificar valores booleanos, nulos o indefinidos, Jest proporciona matchers específicos:
test('null and boolean checks', () => {
const n = null;
const t = true;
const f = false;
const u = undefined;
expect(n).toBeNull();
expect(n).toBeDefined();
expect(u).toBeUndefined();
expect(t).toBeTruthy();
expect(f).toBeFalsy();
});
También podemos usar la negación con .not
para invertir cualquier matcher:
test('using not', () => {
const value = 5;
expect(value).not.toBeNull();
expect(value).not.toBe(10);
});
Matchers para números
Jest ofrece matchers específicos para comparaciones numéricas:
test('numeric comparisons', () => {
const value = 2 + 2;
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(4);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4);
});
Para números de punto flotante, es recomendable usar toBeCloseTo()
en lugar de toBe()
para evitar errores de redondeo:
test('floating point equality', () => {
const value = 0.1 + 0.2;
// Esto fallaría debido a errores de redondeo
// expect(value).toBe(0.3);
// Esto pasa correctamente
expect(value).toBeCloseTo(0.3);
});
Matchers para strings
Para validar cadenas de texto, Jest proporciona varios matchers útiles:
test('string matchers', () => {
const message = 'Hello, world!';
expect(message).toMatch(/Hello/);
expect(message).toMatch('world');
expect(message).toContain('Hello');
expect(message).toHaveLength(13);
expect(message).not.toMatch(/goodbye/i);
});
Matchers para arrays y colecciones
Para verificar el contenido de arrays, Jest ofrece matchers específicos:
test('array matchers', () => {
const shoppingList = [
'apple',
'banana',
'orange'
];
expect(shoppingList).toContain('banana');
expect(shoppingList).toHaveLength(3);
// Para arrays de objetos, usamos arrayContaining
const users = [
{id: 1, name: 'John'},
{id: 2, name: 'Jane'}
];
expect(users).toEqual(
expect.arrayContaining([
expect.objectContaining({name: 'John'})
])
);
});
Verificación de excepciones
Para verificar que una función lanza una excepción específica, usamos toThrow()
:
function validateAge(age) {
if (age < 0) {
throw new Error('Age cannot be negative');
}
return age;
}
test('throws on negative age', () => {
// Importante: debemos pasar una función, no el resultado
expect(() => validateAge(-1)).toThrow();
expect(() => validateAge(-1)).toThrow('Age cannot be negative');
expect(() => validateAge(-1)).toThrow(/negative/);
// Verificamos que no lanza con valor válido
expect(() => validateAge(20)).not.toThrow();
});
Aserciones personalizadas
Jest permite crear matchers personalizados para casos específicos:
// Extendemos Jest con un matcher personalizado
expect.extend({
toBeEvenNumber(received) {
const pass = received % 2 === 0;
if (pass) {
return {
message: () => `Expected ${received} not to be an even number`,
pass: true
};
} else {
return {
message: () => `Expected ${received} to be an even number`,
pass: false
};
}
}
});
test('custom matcher example', () => {
expect(2).toBeEvenNumber();
expect(3).not.toBeEvenNumber();
});
Aserciones asíncronas
Para validar resultados de operaciones asíncronas, Jest proporciona varios enfoques:
// Función que devuelve una promesa
function fetchUser(id) {
return Promise.resolve({ id, name: 'User ' + id });
}
// Usando async/await
test('async/await assertion', async () => {
const user = await fetchUser(1);
expect(user).toEqual({ id: 1, name: 'User 1' });
});
// Usando resolves/rejects
test('resolves matcher', () => {
return expect(fetchUser(1)).resolves.toEqual({
id: 1,
name: 'User 1'
});
});
// Para promesas rechazadas
function fetchFailedUser() {
return Promise.reject(new Error('User not found'));
}
test('rejects matcher', () => {
return expect(fetchFailedUser()).rejects.toThrow('User not found');
});
Aserciones sobre llamadas a funciones
Cuando trabajamos con mocks o spies, podemos verificar cómo se llamaron las funciones:
test('function call assertions', () => {
const mockFn = jest.fn();
mockFn('arg1', 'arg2');
mockFn('arg3', 'arg4');
// Verificamos que la función fue llamada
expect(mockFn).toHaveBeenCalled();
// Verificamos el número exacto de llamadas
expect(mockFn).toHaveBeenCalledTimes(2);
// Verificamos los argumentos de una llamada específica
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
// Verificamos todas las llamadas
expect(mockFn).toHaveBeenNthCalledWith(1, 'arg1', 'arg2');
expect(mockFn).toHaveBeenNthCalledWith(2, 'arg3', 'arg4');
// Verificamos el último llamado
expect(mockFn).toHaveBeenLastCalledWith('arg3', 'arg4');
});
Aserciones con snapshots
Los snapshots son útiles para verificar que la salida de una función no cambia inesperadamente:
function generateUserProfile(user) {
return {
displayName: `${user.firstName} ${user.lastName}`,
email: user.email,
createdAt: new Date().toISOString() // Esto cambiará cada vez
};
}
test('user profile snapshot with dynamic values', () => {
const user = {
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com'
};
const profile = generateUserProfile(user);
// Para valores dinámicos, podemos usar toMatchSnapshot con propiedades específicas
expect({
displayName: profile.displayName,
email: profile.email
}).toMatchSnapshot();
// O usar expect.any() para valores dinámicos
expect({
...profile,
createdAt: expect.any(String)
}).toMatchSnapshot();
});
Consejos para escribir aserciones efectivas
- Sé específico: Usa el matcher más específico para cada caso.
- Verifica un solo concepto por prueba para facilitar la depuración.
- Evita aserciones redundantes que verifican lo mismo de diferentes maneras.
- Usa mensajes personalizados para clarificar fallos complejos:
test('with custom error message', () => {
const result = calculateTotal([10, 20, 30]);
expect(result, 'Sum calculation failed').toBe(60);
});
- Combina matchers para verificaciones complejas:
test('complex object validation', () => {
const user = {
id: 1,
name: 'John',
roles: ['admin', 'editor'],
metadata: {
lastLogin: '2023-01-01'
}
};
expect(user).toEqual(expect.objectContaining({
name: 'John',
roles: expect.arrayContaining(['admin']),
metadata: expect.objectContaining({
lastLogin: expect.stringMatching(/^\d{4}-\d{2}-\d{2}$/)
})
}));
});
Ejemplo de testing unitario con Jest
El testing unitario es una práctica fundamental para garantizar la calidad del código en aplicaciones JavaScript. En esta sección, desarrollaremos un ejemplo completo y práctico de pruebas unitarias con Jest, aplicando los conceptos que hemos aprendido hasta ahora.
Vamos a construir una pequeña biblioteca de utilidades para manipulación de datos y la someteremos a pruebas exhaustivas. Este enfoque práctico te permitirá ver cómo se aplican las técnicas de testing en un escenario realista.
Estructura del proyecto
Primero, definamos la estructura de nuestro proyecto:
project/
├── src/
│ └── utils.js
├── tests/
│ └── utils.test.js
├── package.json
└── jest.config.js
Implementación de la biblioteca de utilidades
Nuestra biblioteca utils.js
contendrá funciones para manipular datos comunes en aplicaciones web:
// src/utils.js
/**
* Formats a price with currency symbol
*/
function formatPrice(price, currency = '$') {
if (typeof price !== 'number') {
throw new TypeError('Price must be a number');
}
return `${currency}${price.toFixed(2)}`;
}
/**
* Filters an array of objects based on a property value
*/
function filterByProperty(array, property, value) {
if (!Array.isArray(array)) {
throw new TypeError('First argument must be an array');
}
return array.filter(item => item[property] === value);
}
/**
* Fetches user data from an API
*/
async function fetchUserData(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.json();
} catch (error) {
throw new Error(`Failed to fetch user data: ${error.message}`);
}
}
/**
* Calculates total price of items in a shopping cart
*/
function calculateCartTotal(items) {
if (!Array.isArray(items)) {
throw new TypeError('Items must be an array');
}
return items.reduce((total, item) => {
const itemTotal = item.price * (item.quantity || 1);
return total + itemTotal;
}, 0);
}
module.exports = {
formatPrice,
filterByProperty,
fetchUserData,
calculateCartTotal
};
Escribiendo las pruebas unitarias
Ahora, vamos a crear pruebas exhaustivas para cada función en utils.test.js
:
// tests/utils.test.js
const {
formatPrice,
filterByProperty,
fetchUserData,
calculateCartTotal
} = require('../src/utils');
// Mock global fetch
global.fetch = jest.fn();
describe('Utils Library', () => {
// Reset mocks between tests
afterEach(() => {
jest.resetAllMocks();
});
describe('formatPrice', () => {
test('formats price with default currency symbol', () => {
expect(formatPrice(10)).toBe('$10.00');
expect(formatPrice(10.5)).toBe('$10.50');
expect(formatPrice(1000)).toBe('$1000.00');
});
test('formats price with custom currency symbol', () => {
expect(formatPrice(10, '€')).toBe('€10.00');
expect(formatPrice(10.5, '£')).toBe('£10.50');
});
test('throws error for non-number inputs', () => {
expect(() => formatPrice('10')).toThrow(TypeError);
expect(() => formatPrice(null)).toThrow('Price must be a number');
expect(() => formatPrice(undefined)).toThrow(TypeError);
});
});
describe('filterByProperty', () => {
const users = [
{ id: 1, name: 'Alice', role: 'admin' },
{ id: 2, name: 'Bob', role: 'user' },
{ id: 3, name: 'Charlie', role: 'user' }
];
test('filters array by property value', () => {
const admins = filterByProperty(users, 'role', 'admin');
expect(admins).toHaveLength(1);
expect(admins[0].name).toBe('Alice');
const regularUsers = filterByProperty(users, 'role', 'user');
expect(regularUsers).toHaveLength(2);
expect(regularUsers[0].name).toBe('Bob');
expect(regularUsers[1].name).toBe('Charlie');
});
test('returns empty array when no matches found', () => {
const result = filterByProperty(users, 'role', 'guest');
expect(result).toEqual([]);
});
test('throws error when first argument is not an array', () => {
expect(() => filterByProperty({}, 'role', 'admin')).toThrow(
'First argument must be an array'
);
});
});
describe('fetchUserData', () => {
test('fetches and returns user data successfully', async () => {
// Mock successful response
const mockUser = { id: 1, name: 'John Doe' };
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => mockUser
});
const result = await fetchUserData(1);
expect(result).toEqual(mockUser);
expect(fetch).toHaveBeenCalledTimes(1);
expect(fetch).toHaveBeenCalledWith('https://api.example.com/users/1');
});
test('throws error when fetch fails', async () => {
// Mock failed response
global.fetch.mockRejectedValueOnce(new Error('Network error'));
await expect(fetchUserData(1)).rejects.toThrow('Failed to fetch user data');
expect(fetch).toHaveBeenCalledTimes(1);
});
test('throws error when response is not ok', async () => {
// Mock HTTP error response
global.fetch.mockResolvedValueOnce({
ok: false,
status: 404
});
await expect(fetchUserData(999)).rejects.toThrow('HTTP error! Status: 404');
expect(fetch).toHaveBeenCalledTimes(1);
});
});
describe('calculateCartTotal', () => {
test('calculates total for items with quantity', () => {
const cart = [
{ id: 1, name: 'Product 1', price: 10, quantity: 2 },
{ id: 2, name: 'Product 2', price: 15, quantity: 1 }
];
expect(calculateCartTotal(cart)).toBe(35); // (10*2) + (15*1)
});
test('assumes quantity of 1 when not specified', () => {
const cart = [
{ id: 1, name: 'Product 1', price: 10 },
{ id: 2, name: 'Product 2', price: 15 }
];
expect(calculateCartTotal(cart)).toBe(25); // 10 + 15
});
test('returns 0 for empty cart', () => {
expect(calculateCartTotal([])).toBe(0);
});
test('throws error when input is not an array', () => {
expect(() => calculateCartTotal(null)).toThrow('Items must be an array');
expect(() => calculateCartTotal('invalid')).toThrow(TypeError);
});
});
});
Ejecutando las pruebas
Para ejecutar estas pruebas, configuramos nuestro archivo package.json
:
{
"name": "utils-library",
"version": "1.0.0",
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"devDependencies": {
"jest": "^29.5.0"
}
}
Y un archivo básico de configuración de Jest:
// jest.config.js
module.exports = {
testEnvironment: 'node',
collectCoverageFrom: ['src/**/*.js'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
Análisis del ejemplo
Este ejemplo demuestra varias técnicas importantes de testing unitario:
- Agrupación lógica de pruebas: Usamos
describe
para organizar las pruebas por función. - Casos de prueba diversos: Probamos tanto casos normales como casos límite y errores.
- Mocking de dependencias externas: Simulamos la API
fetch
para probar código asíncrono sin dependencias reales. - Pruebas de excepciones: Verificamos que las funciones lanzan errores apropiados cuando reciben entradas inválidas.
- Pruebas asíncronas: Demostramos cómo probar funciones que devuelven promesas.
Patrones de testing efectivos
A partir de nuestro ejemplo, podemos identificar algunos patrones efectivos para el testing unitario:
- 1. Prueba una cosa a la vez: Cada test se enfoca en un aspecto específico de la función.
// Mal enfoque (prueba múltiples cosas)
test('formatPrice works correctly', () => {
expect(formatPrice(10)).toBe('$10.00');
expect(formatPrice(10, '€')).toBe('€10.00');
expect(() => formatPrice('10')).toThrow();
});
// Buen enfoque (pruebas separadas)
test('formats with default currency', () => {
expect(formatPrice(10)).toBe('$10.00');
});
test('formats with custom currency', () => {
expect(formatPrice(10, '€')).toBe('€10.00');
});
test('throws for invalid input', () => {
expect(() => formatPrice('10')).toThrow();
});
- 2. Usa nombres descriptivos: Los nombres de las pruebas describen claramente lo que se está probando.
// Nombre poco descriptivo
test('calculateCartTotal test', () => {
// ...
});
// Nombre descriptivo
test('calculateCartTotal handles empty cart correctly', () => {
// ...
});
- 3. Sigue el patrón AAA (Arrange-Act-Assert):
test('filters users by role', () => {
// Arrange (Preparación)
const users = [
{ id: 1, name: 'Alice', role: 'admin' },
{ id: 2, name: 'Bob', role: 'user' }
];
// Act (Ejecución)
const result = filterByProperty(users, 'role', 'admin');
// Assert (Verificación)
expect(result).toHaveLength(1);
expect(result[0].name).toBe('Alice');
});
Estrategias para casos complejos
Nuestro ejemplo también ilustra estrategias para casos más complejos:
- Testing de código asíncrono: Usamos
async/await
y.resolves
/.rejects
para probar promesas.
// Con async/await
test('fetches user data', async () => {
mockFetchResponse({ id: 1, name: 'John' });
const user = await fetchUserData(1);
expect(user.name).toBe('John');
});
// Con resolves
test('fetches user data (alternative)', () => {
mockFetchResponse({ id: 1, name: 'John' });
return expect(fetchUserData(1)).resolves.toHaveProperty('name', 'John');
});
- Simulación de dependencias externas: Usamos mocks para aislar nuestro código de servicios externos.
// Configuración del mock
beforeEach(() => {
global.fetch = jest.fn();
});
// Uso del mock en una prueba específica
test('handles API error', async () => {
global.fetch.mockRejectedValueOnce(new Error('Network error'));
await expect(fetchUserData(1)).rejects.toThrow('Failed to fetch');
});
Mejores prácticas de organización
Para proyectos más grandes, considera estas prácticas de organización:
- Archivos de prueba junto al código: Coloca los archivos de prueba cerca del código que prueban.
- Fixtures compartidos: Extrae datos de prueba comunes a archivos separados.
- Helpers de testing: Crea funciones auxiliares para tareas repetitivas.
// tests/fixtures/users.js
module.exports = {
sampleUsers: [
{ id: 1, name: 'Alice', role: 'admin' },
{ id: 2, name: 'Bob', role: 'user' }
]
};
// tests/helpers/mockApi.js
function mockFetchResponse(data, status = 200) {
global.fetch.mockResolvedValueOnce({
ok: status >= 200 && status < 300,
status,
json: async () => data
});
}
module.exports = { mockFetchResponse };
Integración con flujos de trabajo de desarrollo
Las pruebas unitarias son más efectivas cuando se integran en el flujo de trabajo diario:
- Ejecuta las pruebas automáticamente antes de cada commit usando herramientas como husky
- Configura integración continua para ejecutar pruebas en cada push
- Establece umbrales de cobertura para mantener la calidad del código
Con estas prácticas, las pruebas unitarias se convierten en una herramienta valiosa que mejora la calidad del código y facilita el mantenimiento a largo plazo.
Ejercicios de esta lección Pruebas unitarias
Evalúa tus conocimientos de esta lección Pruebas unitarias con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Array
Modificación de elementos DOM
Encapsulación
Manipulación DOM
Clases y objetos
Uso de operadores
Uso de operadores
Estructuras de control
Funciones
Excepciones
Transformación con map()
Arrays y Métodos
Transformación con map()
Funciones flecha
Async / Await
Polimorfismo
Variables
Selección de elementos DOM
API Fetch
Encapsulación
Mapas con Map
Creación y uso de variables
Polimorfismo
Tipos de datos
Promises
Estructuras de control
Pruebas unitarias
Encapsulación
Inmutabilidad y programación funcional pura
Destructuring de objetos y arrays
Mapas con Map
Funciones flecha
Polimorfismo
Herencia
Array
Transformación con map()
Gestor de tareas con JavaScript
Manipulación DOM
Funciones
Operadores avanzados
Conjuntos con Set
Funciones flecha
Async / Await
Clases y objetos
Métodos de Strings
Creación y uso de variables
Excepciones
Promises
Funciones cierre (closure)
Funciones cierre (closure)
Herencia
Prototipos y cadena de prototipos
Herencia
Estructuras de control
Selección de elementos DOM
Modificación de elementos DOM
Funciones flecha
Filtrado con filter() y find()
Funciones cierre (closure)
Callbacks
Funciones
Mapas con Map
Reducción con reduce()
Callbacks
Manipulación DOM
Introducción al DOM
Expresiones regulares
Promises
Async / Await
Eventos del DOM
Introducción a JavaScript
Async / Await
Excepciones
Promises
Selección de elementos DOM
Filtrado con filter() y find()
Callbacks
Eventos del DOM
Creación de clases y objetos Restaurante
Reducción con reduce()
Filtrado con filter() y find()
Reducción con reduce()
Conjuntos con Set
Herencia de clases
Eventos del DOM
Clases y objetos
Modificación de elementos DOM
Mapas con Map
Proyecto carrito compra agoodshop
Introducción a JavaScript
Filtrado con filter() y find()
Estructuras de control
Funciones
Reducción con reduce()
Proyecto administrador de contactos
Tipos de datos
Clases y objetos
Array
Conjuntos con Set
Todas las lecciones de JavaScript
Accede a todas las lecciones de JavaScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Javascript
Introducción Y Entorno
Introducción A Javascript
Introducción Y Entorno
Introducción A Javascript
Introducción Y Entorno
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Funciones Cierre (Closure)
Sintaxis
Métodos De Strings
Sintaxis
Funciones Cierre (Closure)
Sintaxis
Operadores Avanzados
Sintaxis
Funciones
Sintaxis
Expresiones Regulares
Sintaxis
Estructuras De Control
Sintaxis
Variables
Sintaxis
Arrays Y Métodos
Estructuras De Datos
Conjuntos Con Set
Estructuras De Datos
Mapas Con Map
Estructuras De Datos
Conjuntos Con Set
Estructuras De Datos
Funciones Flecha
Programación Funcional
Filtrado Con Filter() Y Find()
Programación Funcional
Transformación Con Map()
Programación Funcional
Reducción Con Reduce()
Programación Funcional
Funciones Flecha
Programación Funcional
Reducción Con Reduce()
Programación Funcional
Filtrado Con Filter() Y Find()
Programación Funcional
Transformación Con Map()
Programación Funcional
Inmutabilidad Y Programación Funcional Pura
Programación Funcional
Clases Y Objetos
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
This Y Contexto
Programación Orientada A Objetos
Patrón De Módulos Y Namespace
Programación Orientada A Objetos
Clases Y Objetos
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Prototipos Y Cadena De Prototipos
Programación Orientada A Objetos
Destructuring De Objetos Y Arrays
Programación Orientada A Objetos
Manipulación Dom
Dom
Selección De Elementos Dom
Dom
Modificación De Elementos Dom
Dom
Eventos Del Dom
Dom
Modificación De Elementos Dom
Dom
Eventos Del Dom
Dom
Localstorage Y Sessionstorage
Dom
Bom (Browser Object Model)
Dom
Modificación De Elementos Dom
Dom
Selección De Elementos Dom
Dom
Callbacks
Programación Asíncrona
Promises
Programación Asíncrona
Async / Await
Programación Asíncrona
Promises
Programación Asíncrona
Async / Await
Programación Asíncrona
Naturaleza De Js Y Event Loop
Programación Asíncrona
Callbacks
Programación Asíncrona
Websockets
Programación Asíncrona
Módulos En Es6
Construcción
Configuración De Bundlers Como Vite
Construcción
Eslint Y Calidad De Código
Construcción
Npm Y Dependencias
Construcción
Introducción A Pruebas En Js
Testing
Pruebas Unitarias
Testing
En esta lección
Objetivos de aprendizaje de esta lección
Pruebas unitarias