JavaScript

JavaScript

Tutorial JavaScript: Pruebas unitarias

Aprende JavaScript y certifícate

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:

  1. Preparación: configuramos los datos de entrada
  2. Ejecución: llamamos a la función que queremos probar
  3. 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 alias it()): define un caso de prueba individual
  • describe(): agrupa casos de prueba relacionados
  • beforeEach(), afterEach(): ejecuta código antes/después de cada prueba
  • beforeAll(), 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:

  1. Pasar el valor a verificar a la función expect()
  2. 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.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

Plan mensual

19.00 € /mes

Precio normal mensual: 19 €
47 % DE DESCUENTO

Plan anual

10.00 € /mes

Ahorras 108 € al año
Precio normal anual: 120 €
Aprende JavaScript online

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

JavaScript
Puzzle

Modificación de elementos DOM

JavaScript
Proyecto

Encapsulación

JavaScript
Puzzle

Manipulación DOM

JavaScript
Proyecto

Clases y objetos

JavaScript
Código

Uso de operadores

JavaScript
Puzzle

Uso de operadores

JavaScript
Test

Estructuras de control

JavaScript
Test

Funciones

JavaScript
Código

Excepciones

JavaScript
Test

Transformación con map()

JavaScript
Código

Arrays y Métodos

JavaScript
Código

Transformación con map()

JavaScript
Puzzle

Funciones flecha

JavaScript
Test

Async / Await

JavaScript
Código

Polimorfismo

JavaScript
Código

Variables

JavaScript
Código

Selección de elementos DOM

JavaScript
Puzzle

API Fetch

JavaScript
Código

Encapsulación

JavaScript
Test

Mapas con Map

JavaScript
Código

Creación y uso de variables

JavaScript
Puzzle

Polimorfismo

JavaScript
Puzzle

Tipos de datos

JavaScript
Puzzle

Promises

JavaScript
Código

Estructuras de control

JavaScript
Puzzle

Pruebas unitarias

JavaScript
Proyecto

Encapsulación

JavaScript
Código

Inmutabilidad y programación funcional pura

JavaScript
Código

Destructuring de objetos y arrays

JavaScript
Código

Mapas con Map

JavaScript
Código

Funciones flecha

JavaScript
Puzzle

Polimorfismo

JavaScript
Test

Herencia

JavaScript
Código

Array

JavaScript
Código

Transformación con map()

JavaScript
Test

Gestor de tareas con JavaScript

JavaScript
Proyecto

Manipulación DOM

JavaScript
Test

Funciones

JavaScript
Test

Operadores avanzados

JavaScript
Código

Conjuntos con Set

JavaScript
Código

Funciones flecha

JavaScript
Código

Async / Await

JavaScript
Código

Clases y objetos

JavaScript
Código

Métodos de Strings

JavaScript
Código

Creación y uso de variables

JavaScript
Test

Excepciones

JavaScript
Puzzle

Promises

JavaScript
Código

Funciones cierre (closure)

JavaScript
Test

Funciones cierre (closure)

JavaScript
Código

Herencia

JavaScript
Puzzle

Prototipos y cadena de prototipos

JavaScript
Código

Herencia

JavaScript
Test

Estructuras de control

JavaScript
Código

Selección de elementos DOM

JavaScript
Test

Modificación de elementos DOM

JavaScript
Test

Funciones flecha

JavaScript
Código

Filtrado con filter() y find()

JavaScript
Test

Funciones cierre (closure)

JavaScript
Puzzle

Callbacks

JavaScript
Código

Funciones

JavaScript
Puzzle

Mapas con Map

JavaScript
Test

Reducción con reduce()

JavaScript
Test

Callbacks

JavaScript
Puzzle

Manipulación DOM

JavaScript
Puzzle

Introducción al DOM

JavaScript
Proyecto

Expresiones regulares

JavaScript
Código

Promises

JavaScript
Test

Async / Await

JavaScript
Test

Eventos del DOM

JavaScript
Puzzle

Introducción a JavaScript

JavaScript
Puzzle

Async / Await

JavaScript
Puzzle

Excepciones

JavaScript
Código

Promises

JavaScript
Puzzle

Selección de elementos DOM

JavaScript
Proyecto

Filtrado con filter() y find()

JavaScript
Código

Callbacks

JavaScript
Test

Eventos del DOM

JavaScript
Proyecto

Creación de clases y objetos Restaurante

JavaScript
Código

Reducción con reduce()

JavaScript
Código

Filtrado con filter() y find()

JavaScript
Puzzle

Reducción con reduce()

JavaScript
Puzzle

Conjuntos con Set

JavaScript
Puzzle

Herencia de clases

JavaScript
Código

Eventos del DOM

JavaScript
Test

Clases y objetos

JavaScript
Puzzle

Modificación de elementos DOM

JavaScript
Puzzle

Mapas con Map

JavaScript
Puzzle

Proyecto carrito compra agoodshop

JavaScript
Proyecto

Introducción a JavaScript

JavaScript
Test

Filtrado con filter() y find()

JavaScript
Código

Estructuras de control

JavaScript
Código

Funciones

JavaScript
Código

Reducción con reduce()

JavaScript
Código

Proyecto administrador de contactos

JavaScript
Proyecto

Tipos de datos

JavaScript
Test

Clases y objetos

JavaScript
Test

Array

JavaScript
Test

Conjuntos con Set

JavaScript
Test

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

JavaScript

Introducción Y Entorno

Introducción A Javascript

JavaScript

Introducción Y Entorno

Introducción A Javascript

JavaScript

Introducción Y Entorno

Tipos De Datos

JavaScript

Sintaxis

Variables

JavaScript

Sintaxis

Operadores

JavaScript

Sintaxis

Estructuras De Control

JavaScript

Sintaxis

Funciones

JavaScript

Sintaxis

Funciones Cierre (Closure)

JavaScript

Sintaxis

Métodos De Strings

JavaScript

Sintaxis

Funciones Cierre (Closure)

JavaScript

Sintaxis

Operadores Avanzados

JavaScript

Sintaxis

Funciones

JavaScript

Sintaxis

Expresiones Regulares

JavaScript

Sintaxis

Estructuras De Control

JavaScript

Sintaxis

Variables

JavaScript

Sintaxis

Arrays Y Métodos

JavaScript

Estructuras De Datos

Conjuntos Con Set

JavaScript

Estructuras De Datos

Mapas Con Map

JavaScript

Estructuras De Datos

Conjuntos Con Set

JavaScript

Estructuras De Datos

Funciones Flecha

JavaScript

Programación Funcional

Filtrado Con Filter() Y Find()

JavaScript

Programación Funcional

Transformación Con Map()

JavaScript

Programación Funcional

Reducción Con Reduce()

JavaScript

Programación Funcional

Funciones Flecha

JavaScript

Programación Funcional

Reducción Con Reduce()

JavaScript

Programación Funcional

Filtrado Con Filter() Y Find()

JavaScript

Programación Funcional

Transformación Con Map()

JavaScript

Programación Funcional

Inmutabilidad Y Programación Funcional Pura

JavaScript

Programación Funcional

Clases Y Objetos

JavaScript

Programación Orientada A Objetos

Excepciones

JavaScript

Programación Orientada A Objetos

Encapsulación

JavaScript

Programación Orientada A Objetos

Herencia

JavaScript

Programación Orientada A Objetos

Polimorfismo

JavaScript

Programación Orientada A Objetos

Excepciones

JavaScript

Programación Orientada A Objetos

Encapsulación

JavaScript

Programación Orientada A Objetos

Polimorfismo

JavaScript

Programación Orientada A Objetos

Herencia

JavaScript

Programación Orientada A Objetos

This Y Contexto

JavaScript

Programación Orientada A Objetos

Patrón De Módulos Y Namespace

JavaScript

Programación Orientada A Objetos

Clases Y Objetos

JavaScript

Programación Orientada A Objetos

Excepciones

JavaScript

Programación Orientada A Objetos

Prototipos Y Cadena De Prototipos

JavaScript

Programación Orientada A Objetos

Destructuring De Objetos Y Arrays

JavaScript

Programación Orientada A Objetos

Manipulación Dom

JavaScript

Dom

Selección De Elementos Dom

JavaScript

Dom

Modificación De Elementos Dom

JavaScript

Dom

Eventos Del Dom

JavaScript

Dom

Modificación De Elementos Dom

JavaScript

Dom

Eventos Del Dom

JavaScript

Dom

Localstorage Y Sessionstorage

JavaScript

Dom

Bom (Browser Object Model)

JavaScript

Dom

Modificación De Elementos Dom

JavaScript

Dom

Selección De Elementos Dom

JavaScript

Dom

Callbacks

JavaScript

Programación Asíncrona

Promises

JavaScript

Programación Asíncrona

Async / Await

JavaScript

Programación Asíncrona

Promises

JavaScript

Programación Asíncrona

Async / Await

JavaScript

Programación Asíncrona

Naturaleza De Js Y Event Loop

JavaScript

Programación Asíncrona

Callbacks

JavaScript

Programación Asíncrona

Websockets

JavaScript

Programación Asíncrona

Módulos En Es6

JavaScript

Construcción

Configuración De Bundlers Como Vite

JavaScript

Construcción

Eslint Y Calidad De Código

JavaScript

Construcción

Npm Y Dependencias

JavaScript

Construcción

Introducción A Pruebas En Js

JavaScript

Testing

Pruebas Unitarias

JavaScript

Testing

Accede GRATIS a JavaScript y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

Pruebas unitarias