Guía completa de testing en JavaScript
Los tests son un aspecto fundamental del desarrollo de software profesional. Garantizan que tu código funcione como se espera, facilitan el mantenimiento y permiten detectar regresiones. Este artículo explora los distintos enfoques y herramientas para probar eficazmente tus aplicaciones JavaScript.
¿Por qué testear tu código?
- Confianza: seguridad de que tu código funciona correctamente
- Documentación: los tests sirven como documentación viva
- Refactorización segura: modificación del código sin miedo a romper funcionalidades
- Calidad: reducción de bugs y mejora del diseño
- Colaboración: facilita el trabajo en equipo
- Despliegue continuo: automatización de las comprobaciones antes del despliegue
Tipos de tests
Tests unitarios
Los tests unitarios verifican el correcto funcionamiento de las unidades de código más pequeñas (funciones, métodos, clases) de forma aislada.
// Función a testear
function somme(a, b) {
return a + b;
}
// Test unitario con Jest
test('additionne correctement deux nombres', () => {
expect(somme(2, 3)).toBe(5);
expect(somme(-1, 1)).toBe(0);
expect(somme(0, 0)).toBe(0);
});
Tests de integración
Los tests de integración verifican que las distintas partes del sistema funcionan correctamente en conjunto.
// Test de integración con Jest
describe('Service utilisateur', () => {
test('crée un utilisateur et le récupère de la base de données', async () => {
// Crear un usuario
const userId = await userService.createUser({
name: 'Alice',
email: 'alice@example.com'
});
// Recuperar el usuario
const user = await userService.getUserById(userId);
// Verificar que el usuario se recupera correctamente
expect(user).toEqual({
id: userId,
name: 'Alice',
email: 'alice@example.com'
});
});
});
Tests funcionales / End-to-End (E2E)
Los tests E2E verifican el comportamiento de la aplicación desde el punto de vista del usuario final.
// Test E2E con Cypress
describe('Formulaire de connexion', () => {
it('permet à un utilisateur de se connecter', () => {
cy.visit('/login');
cy.get('input[name="email"]').type('utilisateur@exemple.fr');
cy.get('input[name="password"]').type('motdepasse123');
cy.get('button[type="submit"]').click();
// Verificar que el usuario es redirigido al panel de control
cy.url().should('include', '/dashboard');
cy.contains('Bienvenue, Utilisateur').should('be.visible');
});
});
Tests de componentes
En las aplicaciones basadas en componentes (React, Vue, Angular), los tests de componentes verifican el renderizado y el comportamiento de los componentes de interfaz.
// Test de componente React con React Testing Library
import { render, screen, fireEvent } from '@testing-library/react';
import Compteur from './Compteur';
test('incrémente le compteur lorsque le bouton est cliqué', () => {
render(<Compteur />);
// Verificar el estado inicial
expect(screen.getByText('Compteur: 0')).toBeInTheDocument();
// Hacer clic en el botón
fireEvent.click(screen.getByText('Incrémenter'));
// Verificar que el contador se ha incrementado
expect(screen.getByText('Compteur: 1')).toBeInTheDocument();
});
Tests de rendimiento
Los tests de rendimiento verifican que tu aplicación cumple los requisitos de velocidad y eficiencia.
// Test de rendimiento con Lighthouse CI
module.exports = {
ci: {
collect: {
url: ['http://localhost:3000/'],
numberOfRuns: 3,
},
assert: {
assertions: {
'first-contentful-paint': ['warn', { maxNumericValue: 2000 }],
'interactive': ['error', { maxNumericValue: 3000 }],
'max-potential-fid': ['error', { maxNumericValue: 100 }],
},
},
upload: {
target: 'temporary-public-storage',
},
},
};
Tests de seguridad
Los tests de seguridad identifican las posibles vulnerabilidades de tu aplicación.
// Ejemplo con OWASP ZAP (mediante CLI)
// zap-cli quick-scan --self-contained --start-options "-config api.disablekey=true" https://example.com
Frameworks y herramientas de testing populares
Jest
Jest es un framework de testing de JavaScript completo que requiere una configuración mínima.
// Instalación
// npm install --save-dev jest
// Configuración mínima (package.json)
{
"scripts": {
"test": "jest"
}
}
// Ejemplo de test
const { somme } = require('./math');
describe('Fonctions mathématiques', () => {
test('somme additionne deux nombres', () => {
expect(somme(1, 2)).toBe(3);
});
test('somme gère les nombres négatifs', () => {
expect(somme(-1, -2)).toBe(-3);
});
test('somme avec zéro retourne l\'autre nombre', () => {
expect(somme(0, 5)).toBe(5);
});
});
Mocha + Chai
Mocha es un framework de testing flexible, a menudo utilizado junto con Chai para las aserciones.
// Instalación
// npm install --save-dev mocha chai
// Ejemplo de test
const { expect } = require('chai');
const { somme } = require('./math');
describe('Fonctions mathématiques', () => {
it('somme additionne deux nombres', () => {
expect(somme(1, 2)).to.equal(3);
});
it('somme gère les nombres négatifs', () => {
expect(somme(-1, -2)).to.equal(-3);
});
});
Cypress
Cypress es una herramienta moderna de testing E2E que se ejecuta en el navegador.
// Instalación
// npm install --save-dev cypress
// Ejemplo de test
describe('Page d\'accueil', () => {
beforeEach(() => {
cy.visit('/');
});
it('affiche le titre correct', () => {
cy.get('h1').should('contain', 'Bienvenue sur notre site');
});
it('navigue vers la page À propos', () => {
cy.get('nav').contains('À propos').click();
cy.url().should('include', '/about');
cy.get('h1').should('contain', 'À propos de nous');
});
});
Playwright
Playwright es una herramienta de testing E2E compatible con varios navegadores.
// Instalación
// npm install --save-dev @playwright/test
// Ejemplo de test
const { test, expect } = require('@playwright/test');
test('page d\'accueil a le bon titre', async ({ page }) => {
await page.goto('https://example.com');
// Verificar el título
const title = await page.title();
expect(title).toBe('Example Domain');
// Verificar el contenido
const heading = await page.textContent('h1');
expect(heading).toBe('Example Domain');
});
Testing Library
Testing Library es una familia de bibliotecas para probar interfaces de usuario con un enfoque centrado en el usuario.
// Instalación para React
// npm install --save-dev @testing-library/react @testing-library/jest-dom
// Ejemplo de test
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Formulaire from './Formulaire';
test('soumet le formulaire avec les valeurs correctes', () => {
const handleSubmit = jest.fn();
render(<Formulaire onSubmit={handleSubmit} />);
// Rellenar el formulario
fireEvent.change(screen.getByLabelText(/nom/i), {
target: { value: 'Alice' },
});
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'alice@example.com' },
});
// Enviar el formulario
fireEvent.click(screen.getByRole('button', { name: /soumettre/i }));
// Verificar que la función onSubmit se ha llamado con los valores correctos
expect(handleSubmit).toHaveBeenCalledWith({
nom: 'Alice',
email: 'alice@example.com',
});
});
Storybook
Storybook permite desarrollar y probar componentes de interfaz de forma aislada.
// Instalación
// npx sb init
// Ejemplo de story
// Button.stories.js
import React from 'react';
import { Button } from './Button';
export default {
title: 'Components/Button',
component: Button,
argTypes: {
backgroundColor: { control: 'color' },
},
};
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = {
primary: true,
label: 'Button',
};
export const Secondary = Template.bind({});
Secondary.args = {
label: 'Button',
};
Enfoques de testing
Test-Driven Development (TDD)
El TDD sigue un ciclo de tres etapas:
- Red: escribir un test que falle
- Green: escribir el código mínimo para que el test pase
- Refactor: mejorar el código sin cambiar su comportamiento
// Ejemplo de TDD
// 1. Red: escribir un test que falle
test('convertit les degrés Celsius en Fahrenheit', () => {
expect(celsiusToFahrenheit(0)).toBe(32);
expect(celsiusToFahrenheit(100)).toBe(212);
});
// 2. Green: escribir el código mínimo para que el test pase
function celsiusToFahrenheit(celsius) {
return celsius * 9/5 + 32;
}
// 3. Refactor: mejorar el código (si es necesario)
function celsiusToFahrenheit(celsius) {
const factor = 9/5;
const freezingPointDiff = 32;
return celsius * factor + freezingPointDiff;
}
Behavior-Driven Development (BDD)
El BDD se centra en el comportamiento esperado del sistema desde el punto de vista del usuario.
// Ejemplo con Cucumber.js
// Feature: Connexion utilisateur
// En tant qu'utilisateur enregistré
// Je veux pouvoir me connecter
// Afin d'accéder à mon compte
// Scénario: Connexion réussie
// Étant donné que je suis sur la page de connexion
// Quand je saisis mon email "utilisateur@exemple.fr"
// Et je saisis mon mot de passe "motdepasse123"
// Et je clique sur le bouton "Se connecter"
// Alors je devrais être redirigé vers la page "Tableau de bord"
// Et je devrais voir "Bienvenue, Utilisateur"
// Implementación de los pasos
const { Given, When, Then } = require('@cucumber/cucumber');
Given('je suis sur la page de connexion', async function() {
await this.navigateTo('/login');
});
When('je saisis mon email {string}', async function(email) {
await this.fillField('email', email);
});
// ... otros pasos
Mocks, Stubs y Spies
Mocks
Los mocks sustituyen dependencias reales por versiones simuladas.
// Ejemplo con Jest
test('envoie un email de bienvenue lors de l\'inscription', () => {
// Mock del servicio de email
const emailService = {
sendWelcomeEmail: jest.fn()
};
// Sistema bajo test
const userService = new UserService(emailService);
// Acción
userService.registerUser('alice@example.com', 'password123');
// Aserción
expect(emailService.sendWelcomeEmail).toHaveBeenCalledWith('alice@example.com');
});
Stubs
Los stubs proporcionan respuestas predefinidas a las llamadas de función.
// Ejemplo con Sinon.js
const sinon = require('sinon');
const database = require('./database');
test('récupère les données utilisateur', async () => {
// Crear un stub para el método query
const queryStub = sinon.stub(database, 'query');
// Configurar el stub para que devuelva datos específicos
queryStub.withArgs('SELECT * FROM users WHERE id = ?', [42])
.resolves([{ id: 42, name: 'Alice' }]);
// Sistema bajo test
const userService = new UserService(database);
// Acción
const user = await userService.getUserById(42);
// Aserción
expect(user).toEqual({ id: 42, name: 'Alice' });
// Restaurar el stub
queryStub.restore();
});
Spies
Los spies observan las llamadas de función sin modificar su comportamiento.
// Ejemplo con Jest
test('enregistre les erreurs dans la console', () => {
// Crear un spy sobre console.error
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
// Acción que debería generar un error
const calculator = new Calculator();
calculator.divide(10, 0);
// Aserción
expect(errorSpy).toHaveBeenCalledWith('Division par zéro');
// Restaurar el spy
errorSpy.mockRestore();
});
Tests asíncronos
Promises
// Test con Jest
test('récupère les données utilisateur', () => {
return userService.getUserById(42)
.then(user => {
expect(user.name).toBe('Alice');
});
});
// O con async/await
test('récupère les données utilisateur', async () => {
const user = await userService.getUserById(42);
expect(user.name).toBe('Alice');
});
Callbacks
// Test con Jest
test('lit un fichier', done => {
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) {
done(err);
return;
}
try {
expect(data).toContain('Hello, world');
done();
} catch (error) {
done(error);
}
});
});
Cobertura de código
La cobertura de código mide qué parte de tu código se ejecuta durante los tests.
// Configuración de Jest para la cobertura
// package.json
{
"jest": {
"collectCoverage": true,
"collectCoverageFrom": [
"src/**/*.{js,jsx}",
"!**/node_modules/**",
"!**/vendor/**"
],
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
Integración continua (CI)
La integración continua ejecuta automáticamente tus tests con cada modificación del código.
# Ejemplo con GitHub Actions
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Upload coverage
uses: codecov/codecov-action@v2
Buenas prácticas
- Testea primero las funcionalidades críticas: concéntrate en el código más importante
- Mantén los tests simples y legibles: un test debe ser fácil de entender
- Un test, una aserción: cada test debería verificar una sola cosa
- Aísla tus tests: los tests no deben depender unos de otros
- Usa datos de prueba realistas: evita los datos demasiado simplistas
- Evita los tests frágiles: los tests no deben romperse por motivos superficiales
- Ejecuta los tests con regularidad: idealmente con cada modificación del código
- Mantén tus tests: los tests son código y requieren mantenimiento
Errores comunes que debes evitar
- Probar la implementación en lugar del comportamiento: céntrese en lo que hace el código, no en cómo lo hace
- Ignorar los casos límite: pruebe los valores extremos y los casos de error
- Probar únicamente el camino feliz: no olvide probar los casos de fallo
- Pruebas demasiado acopladas al código: las pruebas demasiado específicas se rompen con facilidad al refactorizar
- Descuidar las pruebas de regresión: añada pruebas para los bugs corregidos
- Pruebas lentas: las pruebas lentas desaniman a ejecutarlas con frecuencia
Conclusión
Las pruebas son una inversión que da dividendos a lo largo de toda la vida de un proyecto. Mejoran la calidad del código, facilitan el mantenimiento y permiten despliegues más frecuentes y más fiables.
No existe un enfoque universal para las pruebas. La estrategia óptima depende de la naturaleza de su proyecto, de su equipo y de sus restricciones. Lo importante es empezar por algún sitio y mejorar progresivamente la cobertura de pruebas.
Al adoptar una cultura de pruebas en su equipo y seguir las buenas prácticas presentadas en este artículo, puede mejorar considerablemente la calidad y la mantenibilidad de sus aplicaciones JavaScript.
Acerca de InSkillCoach
Experto en formación y tecnologías
Coach especializado en tecnologías avanzadas e IA, impulsado por GNeurone Inc.
Certificaciones:
- AWS Certified Solutions Architect – Professional
- Certificaciones de Google Cloud
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
Comentarios
Los comentarios funcionan con GitHub Discussions
Inicia sesión con GitHub para participar en la discusión