Saltar a contenido

Mocha Cheatsheet

▪h1 - Pruebas de JavaScript sencillas, flexibles y divertidas "Clase de inscripción" Mocha es un marco de prueba JavaScript rico en funciones que funciona en Node.js y en el navegador, haciendo pruebas asincrónicas simple y divertida. Las pruebas de Mocha se ejecutan en serie, lo que permite la presentación de informes flexibles y precisos, mientras se registran excepciones no traídas a los casos de prueba correctos. ▪/p] ■/div titulada

########################################################################################################################################################################################################################################################## Copiar todos los comandos
########################################################################################################################################################################################################################################################## Generar PDF seleccionado/button

■/div titulada ■/div titulada

Cuadro de contenidos

Instalación

Instalación básica

# Install Mocha globally
npm install -g mocha

# Install Mocha locally (recommended)
npm install --save-dev mocha

# Install with assertion library
npm install --save-dev mocha chai

# Install with additional tools
npm install --save-dev mocha chai sinon nyc

Configuración de proyectos

# Initialize new project
mkdir my-mocha-project
cd my-mocha-project
npm init -y

# Install dependencies
npm install --save-dev mocha chai

# Create test directory
mkdir test

# Create first test file
touch test/test.js

Paquete.json Configuración

{
  "scripts": {
    "test": "mocha",
    "test:watch": "mocha --watch",
    "test:coverage": "nyc mocha",
    "test:reporter": "mocha --reporter spec",
    "test:grep": "mocha --grep 'pattern'",
    "test:timeout": "mocha --timeout 5000"
  },
  "devDependencies": {
    "mocha": "^10.0.0",
    "chai": "^4.3.0",
    "sinon": "^15.0.0",
    "nyc": "^15.1.0"
  }
}

Estructura del directorio

my-project/
├── lib/
│   ├── calculator.js
│   └── utils.js
├── test/
│   ├── calculator.test.js
│   ├── utils.test.js
│   └── helpers/
│       └── setup.js
├── .mocharc.json
├── package.json
└── README.md

Comienzo

Primera prueba

// lib/calculator.js
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

function multiply(a, b) {
  return a * b;
}

function divide(a, b) {
  if (b === 0) {
    throw new Error('Division by zero');
  }
  return a / b;
}

module.exports = { add, subtract, multiply, divide };
// test/calculator.test.js
const { expect } = require('chai');
const { add, subtract, multiply, divide } = require('../lib/calculator');

describe('Calculator', function() {
  describe('Addition', function() {
    it('should add two positive numbers', function() {
      expect(add(2, 3)).to.equal(5);
    });

    it('should add negative numbers', function() {
      expect(add(-2, -3)).to.equal(-5);
    });

    it('should add zero', function() {
      expect(add(5, 0)).to.equal(5);
    });
  });

  describe('Division', function() {
    it('should divide positive numbers', function() {
      expect(divide(10, 2)).to.equal(5);
    });

    it('should throw error for division by zero', function() {
      expect(() => divide(10, 0)).to.throw('Division by zero');
    });
  });
});

Pruebas de ejecución

# Run all tests
npm test

# Run specific test file
npx mocha test/calculator.test.js

# Run tests with pattern
npx mocha test/**/*.test.js

# Run tests with grep
npx mocha --grep "Addition"

# Run tests in watch mode
npx mocha --watch

# Run tests with reporter
npx mocha --reporter json

Sintaxis de prueba básica

// Test suite
describe('Feature Name', function() {
  // Test case
  it('should do something', function() {
    // Test implementation
  });

  // Pending test
  it('should do something else');

  // Skipped test
  it.skip('should skip this test', function() {
    // This won't run
  });

  // Only run this test
  it.only('should only run this test', function() {
    // Only this test will run
  });
});

Estructura de ensayo

Describir bloques

describe('User Management', function() {
  describe('User Creation', function() {
    it('should create a new user', function() {
      // Test implementation
    });

    it('should validate user data', function() {
      // Test implementation
    });
  });

  describe('User Authentication', function() {
    it('should authenticate valid user', function() {
      // Test implementation
    });

    it('should reject invalid credentials', function() {
      // Test implementation
    });
  });
});

Describes anidados

describe('API', function() {
  describe('Users Endpoint', function() {
    describe('GET /users', function() {
      it('should return all users', function() {
        // Test implementation
      });

      it('should return 200 status', function() {
        // Test implementation
      });
    });

    describe('POST /users', function() {
      it('should create new user', function() {
        // Test implementation
      });

      it('should return 201 status', function() {
        // Test implementation
      });
    });
  });
});

Organización de los ensayos

// Good: Descriptive test names
describe('Email Validator', function() {
  it('should return true for valid email addresses', function() {
    // Test implementation
  });

  it('should return false for invalid email addresses', function() {
    // Test implementation
  });

  it('should handle edge cases like empty strings', function() {
    // Test implementation
  });
});

// Good: Group related functionality
describe('Shopping Cart', function() {
  describe('Adding Items', function() {
    it('should add item to cart');
    it('should update cart total');
    it('should increment item count');
  });

  describe('Removing Items', function() {
    it('should remove item from cart');
    it('should update cart total');
    it('should decrement item count');
  });
});

Test Context

describe('User Service', function() {
  context('when user exists', function() {
    it('should return user data', function() {
      // Test implementation
    });

    it('should update user successfully', function() {
      // Test implementation
    });
  });

  context('when user does not exist', function() {
    it('should return null', function() {
      // Test implementation
    });

    it('should throw error on update', function() {
      // Test implementation
    });
  });
});

Ganchos

Ganchos básicos

describe('Database Tests', function() {
  // Runs once before all tests in this describe block
  before(function() {
    console.log('Setting up database connection');
    // Setup code here
  });

  // Runs once after all tests in this describe block
  after(function() {
    console.log('Closing database connection');
    // Cleanup code here
  });

  // Runs before each test in this describe block
  beforeEach(function() {
    console.log('Preparing test data');
    // Setup for each test
  });

  // Runs after each test in this describe block
  afterEach(function() {
    console.log('Cleaning up test data');
    // Cleanup after each test
  });

  it('should save user', function() {
    // Test implementation
  });

  it('should find user', function() {
    // Test implementation
  });
});

Async Hooks

describe('Async Setup', function() {
  before(async function() {
    this.timeout(5000); // Increase timeout for setup

    // Async setup
    this.database = await connectToDatabase();
    this.server = await startServer();
  });

  after(async function() {
    // Async cleanup
    await this.database.close();
    await this.server.stop();
  });

  beforeEach(async function() {
    // Clear database before each test
    await this.database.clear();

    // Seed test data
    await this.database.seed({
      users: [
        { name: 'John', email: 'john@example.com' },
        { name: 'Jane', email: 'jane@example.com' }
      ]
    });
  });

  it('should find users', async function() {
    const users = await this.database.findAll('users');
    expect(users).to.have.length(2);
  });
});

Hook Inheritance

describe('Parent Suite', function() {
  before(function() {
    console.log('Parent before');
  });

  beforeEach(function() {
    console.log('Parent beforeEach');
  });

  describe('Child Suite', function() {
    before(function() {
      console.log('Child before');
    });

    beforeEach(function() {
      console.log('Child beforeEach');
    });

    it('should inherit hooks', function() {
      // Execution order:
      // 1. Parent before
      // 2. Child before
      // 3. Parent beforeEach
      // 4. Child beforeEach
      // 5. Test execution
    });
  });
});

Ganchos condicionales

describe('Conditional Setup', function() {
  before(function() {
    if (process.env.NODE_ENV === 'test') {
      // Only run in test environment
      this.mockServer = startMockServer();
    }
  });

  beforeEach(function() {
    // Skip setup for specific tests
    if (this.currentTest.title.includes('integration')) {
      this.skip();
    }
  });

  it('should run unit test', function() {
    // This will run
  });

  it('should run integration test', function() {
    // This will be skipped by beforeEach
  });
});

Asserciones

Chai Assertions

const { expect } = require('chai');

describe('Chai Assertions', function() {
  it('should test equality', function() {
    expect(2 + 2).to.equal(4);
    expect({ name: 'John' }).to.deep.equal({ name: 'John' });
    expect([1, 2, 3]).to.deep.equal([1, 2, 3]);
  });

  it('should test truthiness', function() {
    expect(true).to.be.true;
    expect(false).to.be.false;
    expect(null).to.be.null;
    expect(undefined).to.be.undefined;
    expect('hello').to.exist;
  });

  it('should test types', function() {
    expect('hello').to.be.a('string');
    expect(42).to.be.a('number');
    expect([]).to.be.an('array');
    expect({}).to.be.an('object');
    expect(() => {}).to.be.a('function');
  });

  it('should test properties', function() {
    const obj = { name: 'John', age: 30 };
    expect(obj).to.have.property('name');
    expect(obj).to.have.property('age', 30);
    expect(obj).to.have.all.keys('name', 'age');
  });

  it('should test arrays', function() {
    const arr = [1, 2, 3, 4, 5];
    expect(arr).to.have.length(5);
    expect(arr).to.include(3);
    expect(arr).to.include.members([2, 4]);
    expect(arr).to.have.ordered.members([1, 2, 3, 4, 5]);
  });

  it('should test strings', function() {
    expect('hello world').to.contain('world');
    expect('hello world').to.match(/^hello/);
    expect('hello world').to.have.length(11);
  });
});

Aserciones aduaneras

const { expect } = require('chai');

// Extend Chai with custom assertion
chai.use(function(chai, utils) {
  chai.Assertion.addMethod('validEmail', function() {
    const obj = this._obj;
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

    this.assert(
      emailRegex.test(obj),
      'expected #{this} to be a valid email',
      'expected #{this} not to be a valid email'
    );
  });
});

describe('Custom Assertions', function() {
  it('should validate email addresses', function() {
    expect('user@example.com').to.be.validEmail;
    expect('invalid-email').to.not.be.validEmail;
  });
});

Deberían las aserciones de estilo

const should = require('chai').should();

describe('Should Style', function() {
  it('should use should syntax', function() {
    const user = { name: 'John', age: 30 };

    user.should.be.an('object');
    user.should.have.property('name', 'John');
    user.age.should.equal(30);

    const numbers = [1, 2, 3];
    numbers.should.have.length(3);
    numbers.should.include(2);
  });
});

Assert Style Asserciones

const assert = require('chai').assert;

describe('Assert Style', function() {
  it('should use assert syntax', function() {
    const user = { name: 'John', age: 30 };

    assert.isObject(user);
    assert.property(user, 'name');
    assert.equal(user.name, 'John');
    assert.equal(user.age, 30);

    const numbers = [1, 2, 3];
    assert.lengthOf(numbers, 3);
    assert.include(numbers, 2);
  });
});

Pruebas Async

Promesas

describe('Promise Testing', function() {
  it('should resolve promise', function() {
    return fetchUser(1).then(user => {
      expect(user.name).to.equal('John');
    });
  });

  it('should reject promise', function() {
    return fetchUser(-1).catch(error => {
      expect(error.message).to.equal('User not found');
    });
  });

  it('should use async/await', async function() {
    const user = await fetchUser(1);
    expect(user.name).to.equal('John');
  });

  it('should handle async errors', async function() {
    try {
      await fetchUser(-1);
      throw new Error('Should have thrown');
    } catch (error) {
      expect(error.message).to.equal('User not found');
    }
  });
});

Callbacks

describe('Callback Testing', function() {
  it('should handle callbacks with done', function(done) {
    fetchUserCallback(1, (error, user) => {
      if (error) return done(error);

      try {
        expect(user.name).to.equal('John');
        done();
      } catch (assertionError) {
        done(assertionError);
      }
    });
  });

  it('should handle callback errors', function(done) {
    fetchUserCallback(-1, (error, user) => {
      try {
        expect(error).to.exist;
        expect(error.message).to.equal('User not found');
        expect(user).to.not.exist;
        done();
      } catch (assertionError) {
        done(assertionError);
      }
    });
  });
});

Timeouts

describe('Timeout Testing', function() {
  // Set timeout for entire suite
  this.timeout(5000);

  it('should complete within timeout', function(done) {
    // Set timeout for specific test
    this.timeout(2000);

    setTimeout(() => {
      expect(true).to.be.true;
      done();
    }, 1000);
  });

  it('should handle slow operations', async function() {
    this.timeout(10000);

    const result = await slowOperation();
    expect(result).to.exist;
  });

  it('should disable timeout', function(done) {
    this.timeout(0); // Disable timeout

    // Very slow operation
    setTimeout(done, 30000);
  });
});

Retries

describe('Retry Testing', function() {
  // Retry failed tests
  this.retries(3);

  it('should retry flaky test', function() {
    // This test might fail randomly
    if (Math.random() < 0.7) {
      throw new Error('Random failure');
    }
    expect(true).to.be.true;
  });

  it('should retry specific test', function() {
    this.retries(5);

    // Test implementation
  });
});

Configuración

Mocha Configuration File

// .mocharc.json
{
  "spec": "test/**/*.test.js",
  "require": ["test/helpers/setup.js"],
  "timeout": 5000,
  "reporter": "spec",
  "recursive": true,
  "exit": true,
  "bail": false,
  "grep": "",
  "invert": false,
  "checkLeaks": true,
  "globals": ["expect", "sinon"],
  "retries": 0,
  "slow": 75,
  "ui": "bdd"
}

JavaScript Configuración

// .mocharc.js
module.exports = {
  spec: 'test/**/*.test.js',
  require: ['test/helpers/setup.js'],
  timeout: 5000,
  reporter: 'spec',
  recursive: true,
  exit: true,

  // Environment-specific configuration
  ...(process.env.NODE_ENV === 'ci' && {
    reporter: 'json',
    timeout: 10000,
    retries: 2
  })
};

Configuración de archivo

// test/helpers/setup.js
const chai = require('chai');
const sinon = require('sinon');

// Global setup
global.expect = chai.expect;
global.sinon = sinon;

// Chai plugins
chai.use(require('chai-as-promised'));
chai.use(require('sinon-chai'));

// Global hooks
before(function() {
  console.log('Global setup');
});

after(function() {
  console.log('Global cleanup');
});

beforeEach(function() {
  // Reset sinon stubs/spies
  sinon.restore();
});

Medio ambiente

// test/helpers/setup.js
process.env.NODE_ENV = 'test';
process.env.DATABASE_URL = 'mongodb://localhost:27017/test';
process.env.API_URL = 'http://localhost:3001';

// Load environment-specific config
if (process.env.CI) {
  // CI-specific setup
  process.env.TIMEOUT = '10000';
} else {
  // Local development setup
  process.env.TIMEOUT = '5000';
}

Configuraciones múltiples

// mocha.config.js
const baseConfig = {
  timeout: 5000,
  recursive: true,
  exit: true
};

const configs = {
  unit: {
    ...baseConfig,
    spec: 'test/unit/**/*.test.js',
    reporter: 'spec'
  },

  integration: {
    ...baseConfig,
    spec: 'test/integration/**/*.test.js',
    timeout: 10000,
    reporter: 'json'
  },

  e2e: {
    ...baseConfig,
    spec: 'test/e2e/**/*.test.js',
    timeout: 30000,
    reporter: 'tap'
  }
};

module.exports = configs[process.env.TEST_TYPE] || configs.unit;

Reporteros

Reporteros incorporados

# Spec reporter (default)
npx mocha --reporter spec

# JSON reporter
npx mocha --reporter json

# TAP reporter
npx mocha --reporter tap

# Dot reporter
npx mocha --reporter dot

# Progress reporter
npx mocha --reporter progress

# Min reporter
npx mocha --reporter min

# Landing reporter
npx mocha --reporter landing

# List reporter
npx mocha --reporter list

Custom Reporter

// reporters/custom-reporter.js
function CustomReporter(runner) {
  const stats = runner.stats;

  runner.on('start', function() {
    console.log('🚀 Starting test run...');
  });

  runner.on('suite', function(suite) {
    if (suite.root) return;
    console.log(`📁 ${suite.title}`);
  });

  runner.on('test', function(test) {
    console.log(`  ▶️  ${test.title}`);
  });

  runner.on('pass', function(test) {
    console.log(`  ✅ ${test.title} (${test.duration}ms)`);
  });

  runner.on('fail', function(test, err) {
    console.log(`  ❌ ${test.title}`);
    console.log(`     ${err.message}`);
  });

  runner.on('end', function() {
    console.log(`🏁 ${stats.passes} passed, ${stats.failures} failed`);
  });
}

module.exports = CustomReporter;

// Usage
// npx mocha --reporter ./reporters/custom-reporter.js

Reportero con salida de archivo

// reporters/file-reporter.js
const fs = require('fs');
const path = require('path');

function FileReporter(runner, options) {
  const reportPath = options.reporterOptions?.output || 'test-results.json';
  const results = {
    stats: {},
    tests: [],
    failures: []
  };

  runner.on('start', function() {
    results.stats.start = new Date();
  });

  runner.on('pass', function(test) {
    results.tests.push({
      title: test.title,
      fullTitle: test.fullTitle(),
      duration: test.duration,
      state: 'passed'
    });
  });

  runner.on('fail', function(test, err) {
    results.tests.push({
      title: test.title,
      fullTitle: test.fullTitle(),
      duration: test.duration,
      state: 'failed',
      error: err.message
    });

    results.failures.push({
      title: test.fullTitle(),
      error: err.message,
      stack: err.stack
    });
  });

  runner.on('end', function() {
    results.stats.end = new Date();
    results.stats.duration = results.stats.end - results.stats.start;
    results.stats.passes = runner.stats.passes;
    results.stats.failures = runner.stats.failures;

    fs.writeFileSync(reportPath, JSON.stringify(results, null, 2));
    console.log(`Report written to ${reportPath}`);
  });
}

module.exports = FileReporter;

Multiple Reporters

# Install multi-reporter
npm install --save-dev mocha-multi-reporters

# Configuration file
# multi-reporter-config.json
{
  "reporterEnabled": "spec,json,tap",
  "jsonReporterOptions": {
    "output": "test-results.json"
  },
  "tapReporterOptions": {
    "output": "test-results.tap"
  }
}

# Run with multiple reporters
npx mocha --reporter mocha-multi-reporters --reporter-options configFile=multi-reporter-config.json

Pruebas del navegador

Configuración del navegador

<!-- test/browser/index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Mocha Browser Tests</title>
  <link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css">
</head>
<body>
  <div id="mocha"></div>

  <!-- Mocha -->
  <script src="https://unpkg.com/mocha/mocha.js"></script>

  <!-- Chai -->
  <script src="https://unpkg.com/chai/chai.js"></script>

  <!-- Setup -->
  <script>
    mocha.setup('bdd');
    const expect = chai.expect;
  </script>

  <!-- Your code -->
  <script src="../lib/calculator.js"></script>

  <!-- Your tests -->
  <script src="calculator.test.js"></script>

  <!-- Run tests -->
  <script>
    mocha.run();
  </script>
</body>
</html>

Archivo de prueba de navegador

// test/browser/calculator.test.js
describe('Calculator (Browser)', function() {
  it('should add numbers', function() {
    expect(add(2, 3)).to.equal(5);
  });

  it('should work with DOM', function() {
    const div = document.createElement('div');
    div.textContent = 'Hello World';
    document.body.appendChild(div);

    expect(div.textContent).to.equal('Hello World');

    document.body.removeChild(div);
  });

  it('should test localStorage', function() {
    localStorage.setItem('test', 'value');
    expect(localStorage.getItem('test')).to.equal('value');
    localStorage.removeItem('test');
  });
});

Integración Webpack

// webpack.config.js
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './test/browser/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'test-bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
};

Puppeteer Integration

// test/browser/puppeteer.test.js
const puppeteer = require('puppeteer');
const { expect } = require('chai');

describe('Browser Tests with Puppeteer', function() {
  let browser, page;

  before(async function() {
    browser = await puppeteer.launch();
    page = await browser.newPage();
  });

  after(async function() {
    await browser.close();
  });

  it('should run tests in browser', async function() {
    await page.goto('file://' + __dirname + '/index.html');

    // Wait for tests to complete
    await page.waitForFunction(() => {
      return window.mochaResults && window.mochaResults.complete;
    });

    const results = await page.evaluate(() => window.mochaResults);
    expect(results.failures).to.equal(0);
  });
});

Mocking

Sinon Integration

const sinon = require('sinon');
const { expect } = require('chai');

describe('Mocking with Sinon', function() {
  afterEach(function() {
    sinon.restore();
  });

  it('should mock functions', function() {
    const callback = sinon.fake();
    const proxy = sinon.fake.returns(42);

    callback('hello', 'world');
    expect(callback.calledWith('hello', 'world')).to.be.true;

    expect(proxy()).to.equal(42);
  });

  it('should stub methods', function() {
    const user = {
      getName: () => 'John',
      setName: (name) => { this.name = name; }
    };

    const stub = sinon.stub(user, 'getName').returns('Jane');

    expect(user.getName()).to.equal('Jane');
    expect(stub.calledOnce).to.be.true;
  });

  it('should spy on methods', function() {
    const user = {
      getName: () => 'John'
    };

    const spy = sinon.spy(user, 'getName');

    user.getName();
    user.getName();

    expect(spy.calledTwice).to.be.true;
  });
});

HTTP Mocking

const nock = require('nock');
const axios = require('axios');
const { expect } = require('chai');

describe('HTTP Mocking', function() {
  afterEach(function() {
    nock.cleanAll();
  });

  it('should mock HTTP requests', async function() {
    nock('https://api.example.com')
      .get('/users/1')
      .reply(200, { id: 1, name: 'John' });

    const response = await axios.get('https://api.example.com/users/1');
    expect(response.data.name).to.equal('John');
  });

  it('should mock POST requests', async function() {
    nock('https://api.example.com')
      .post('/users', { name: 'Jane' })
      .reply(201, { id: 2, name: 'Jane' });

    const response = await axios.post('https://api.example.com/users', {
      name: 'Jane'
    });

    expect(response.status).to.equal(201);
    expect(response.data.id).to.equal(2);
  });

  it('should mock with delays', async function() {
    nock('https://api.example.com')
      .get('/slow')
      .delay(1000)
      .reply(200, { message: 'slow response' });

    const start = Date.now();
    await axios.get('https://api.example.com/slow');
    const duration = Date.now() - start;

    expect(duration).to.be.at.least(1000);
  });
});

Módulo Mocking

const proxyquire = require('proxyquire');
const { expect } = require('chai');

describe('Module Mocking', function() {
  it('should mock required modules', function() {
    const dbMock = {
      findUser: sinon.stub().returns({ id: 1, name: 'John' })
    };

    const UserService = proxyquire('../lib/user-service', {
      './database': dbMock
    });

    const user = UserService.getUser(1);
    expect(user.name).to.equal('John');
    expect(dbMock.findUser.calledWith(1)).to.be.true;
  });

  it('should mock with multiple dependencies', function() {
    const mocks = {
      './database': {
        connect: sinon.stub(),
        query: sinon.stub().returns([])
      },
      './logger': {
        log: sinon.stub(),
        error: sinon.stub()
      }
    };

    const Service = proxyquire('../lib/service', mocks);

    Service.initialize();
    expect(mocks['./database'].connect.called).to.be.true;
    expect(mocks['./logger'].log.called).to.be.true;
  });
});

Características avanzadas

Pruebas paralelas

# Install parallel testing
npm install --save-dev mocha-parallel-tests

# Run tests in parallel
npx mocha-parallel-tests

# Specify max parallel processes
npx mocha-parallel-tests --max-parallel 4

Filtro de prueba

# Run tests matching pattern
npx mocha --grep "should add"

# Run tests NOT matching pattern
npx mocha --grep "should add" --invert

# Run tests in specific files
npx mocha test/unit/**/*.test.js

# Run tests with tags
npx mocha --grep "@slow"
// Tag tests with comments
describe('Calculator', function() {
  it('should add quickly @fast', function() {
    // Fast test
  });

  it('should handle complex calculations @slow', function() {
    // Slow test
  });
});
```_

### Generación de prueba dinámica
```javascript
describe('Dynamic Tests', function() {
  const testCases = [
    { input: [2, 3], expected: 5 },
    { input: [5, 7], expected: 12 },
    { input: [-1, 1], expected: 0 },
    { input: [0, 0], expected: 0 }
  ];

  testCases.forEach(({ input, expected }) => {
    it(`should add ${input[0]} + ${input[1]} = ${expected}`, function() {
      expect(add(input[0], input[1])).to.equal(expected);
    });
  });
});

// Async dynamic tests
describe('API Endpoints', function() {
  let endpoints;

  before(async function() {
    endpoints = await loadEndpointsConfig();
  });

  function createEndpointTest(endpoint) {
    it(`should respond to ${endpoint.method} ${endpoint.path}`, async function() {
      const response = await request(app)
        [endpoint.method.toLowerCase()](endpoint.path);

      expect(response.status).to.equal(endpoint.expectedStatus);
    });
  }

  // Generate tests after loading config
  before(function() {
    endpoints.forEach(createEndpointTest);
  });
});

Test Utilities

// test/helpers/utils.js
const { expect } = require('chai');

function expectAsync(promise) {
  return {
    toResolve: async () => {
      try {
        const result = await promise;
        return result;
      } catch (error) {
        throw new Error(`Expected promise to resolve, but it rejected with: ${error.message}`);
      }
    },

    toReject: async (expectedError) => {
      try {
        await promise;
        throw new Error('Expected promise to reject, but it resolved');
      } catch (error) {
        if (expectedError) {
          expect(error.message).to.include(expectedError);
        }
        return error;
      }
    }
  };
}

function createUser(overrides = {}) {
  return {
    id: Math.floor(Math.random() * 1000),
    name: 'Test User',
    email: 'test@example.com',
    ...overrides
  };
}

module.exports = { expectAsync, createUser };

Factores de datos de prueba

// test/factories/user-factory.js
class UserFactory {
  static create(overrides = {}) {
    return {
      id: this.generateId(),
      name: 'John Doe',
      email: 'john@example.com',
      age: 30,
      active: true,
      createdAt: new Date(),
      ...overrides
    };
  }

  static createMany(count, overrides = {}) {
    return Array.from({ length: count }, (_, index) => 
      this.create({ id: index + 1, ...overrides })
    );
  }

  static generateId() {
    return Math.floor(Math.random() * 10000);
  }
}

module.exports = UserFactory;

// Usage in tests
const UserFactory = require('./factories/user-factory');

describe('User Service', function() {
  it('should process user', function() {
    const user = UserFactory.create({ name: 'Jane' });
    const result = processUser(user);
    expect(result.displayName).to.equal('Jane');
  });

  it('should handle multiple users', function() {
    const users = UserFactory.createMany(5);
    expect(users).to.have.length(5);
  });
});

Plugins

Plugins populares

# Chai plugins
npm install --save-dev chai-as-promised chai-http chai-string

# Sinon integration
npm install --save-dev sinon-chai

# Coverage
npm install --save-dev nyc

# Parallel testing
npm install --save-dev mocha-parallel-tests

# Multi-reporters
npm install --save-dev mocha-multi-reporters

Chai Plugins Usage

const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
const chaiHttp = require('chai-http');
const chaiString = require('chai-string');

chai.use(chaiAsPromised);
chai.use(chaiHttp);
chai.use(chaiString);

const { expect } = chai;

describe('Chai Plugins', function() {
  it('should test promises', async function() {
    await expect(Promise.resolve('success')).to.eventually.equal('success');
    await expect(Promise.reject(new Error('fail'))).to.be.rejected;
  });

  it('should test HTTP', function() {
    return chai.request(app)
      .get('/api/users')
      .then(res => {
        expect(res).to.have.status(200);
        expect(res).to.be.json;
        expect(res.body).to.be.an('array');
      });
  });

  it('should test strings', function() {
    expect('Hello World').to.startWith('Hello');
    expect('Hello World').to.endWith('World');
    expect('Hello World').to.equalIgnoreCase('hello world');
  });
});
```_

### Desarrollo de Plugin personalizado
```javascript
// chai-custom-plugin.js
module.exports = function(chai, utils) {
  chai.Assertion.addMethod('validEmail', function() {
    const obj = this._obj;
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

    this.assert(
      emailRegex.test(obj),
      'expected #{this} to be a valid email',
      'expected #{this} not to be a valid email'
    );
  });

  chai.Assertion.addMethod('between', function(min, max) {
    const obj = this._obj;

    this.assert(
      obj >= min && obj <= max,
      'expected #{this} to be between #{exp1} and #{exp2}',
      'expected #{this} not to be between #{exp1} and #{exp2}',
      `${min} and ${max}`
    );
  });
};

// Usage
const chai = require('chai');
chai.use(require('./chai-custom-plugin'));

describe('Custom Plugin', function() {
  it('should validate emails', function() {
    expect('user@example.com').to.be.validEmail;
  });

  it('should check ranges', function() {
    expect(5).to.be.between(1, 10);
  });
});

CI/CD Integration

GitHub Actions

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [16, 18, 20]

    steps:
    - uses: actions/checkout@v3

    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Run tests
      run: npm test

    - name: Run coverage
      run: npm run test:coverage

    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage/lcov.info

Jenkins Pipeline

// Jenkinsfile
pipeline {
    agent any

    tools {
        nodejs '18'
    }

    stages {
        stage('Install') {
            steps {
                sh 'npm ci'
            }
        }

        stage('Test') {
            steps {
                sh 'npm test'
            }
            post {
                always {
                    publishTestResults testResultsPattern: 'test-results.xml'
                }
            }
        }

        stage('Coverage') {
            steps {
                sh 'npm run test:coverage'
            }
            post {
                always {
                    publishHTML([
                        allowMissing: false,
                        alwaysLinkToLastBuild: true,
                        keepAll: true,
                        reportDir: 'coverage',
                        reportFiles: 'index.html',
                        reportName: 'Coverage Report'
                    ])
                }
            }
        }
    }
}

Docker Integration

# Dockerfile.test
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

CMD ["npm", "test"]
# docker-compose.test.yml
version: '3.8'
services:
  test:
    build:
      context: .
      dockerfile: Dockerfile.test
    environment:
      - NODE_ENV=test
    volumes:
      - ./coverage:/app/coverage

Buenas prácticas

Organización de los ensayos

// Good: Clear test structure
describe('UserService', function() {
  describe('#createUser', function() {
    context('with valid data', function() {
      it('should create user successfully', function() {
        // Test implementation
      });

      it('should return user with ID', function() {
        // Test implementation
      });
    });

    context('with invalid data', function() {
      it('should throw validation error', function() {
        // Test implementation
      });
    });
  });
});

// Good: Descriptive test names
it('should return 404 when user does not exist', function() {
  // Test implementation
});

it('should update user email when valid email provided', function() {
  // Test implementation
});

Gestión de datos de prueba

// Good: Use factories for test data
const UserFactory = require('./factories/user-factory');

describe('User Tests', function() {
  it('should validate user', function() {
    const user = UserFactory.create({ email: 'invalid-email' });
    expect(() => validateUser(user)).to.throw();
  });
});

// Good: Clean up after tests
describe('Database Tests', function() {
  afterEach(async function() {
    await cleanupDatabase();
  });
});

Prácticas óptimas de Async

// Good: Proper async handling
describe('Async Tests', function() {
  it('should handle promises', async function() {
    const result = await asyncOperation();
    expect(result).to.exist;
  });

  it('should handle errors', async function() {
    await expect(failingOperation()).to.be.rejected;
  });
});

// Good: Proper timeout handling
describe('Slow Tests', function() {
  this.timeout(10000);

  it('should complete slow operation', async function() {
    const result = await slowOperation();
    expect(result).to.exist;
  });
});

Debugging

Modo de depuración

# Run with debug output
DEBUG=mocha* npm test

# Run specific test with debug
npx mocha --grep "specific test" --inspect-brk

# Debug with VS Code
# Add to launch.json
{
  "type": "node",
  "request": "launch",
  "name": "Mocha Debug",
  "program": "${workspaceFolder}/node_modules/.bin/mocha",
  "args": ["--timeout", "999999", "--colors", "${workspaceFolder}/test"],
  "console": "integratedTerminal",
  "internalConsoleOptions": "neverOpen"
}

Depuración de pruebas

describe('Debug Tests', function() {
  it('should debug test', function() {
    const data = { name: 'John', age: 30 };

    console.log('Debug data:', data);
    debugger; // Breakpoint for debugging

    expect(data.name).to.equal('John');
  });

  it('should log test progress', function() {
    console.log('Step 1: Setup');
    const user = createUser();

    console.log('Step 2: Process');
    const result = processUser(user);

    console.log('Step 3: Verify');
    expect(result).to.exist;
  });
});

Depuración de errores

describe('Error Debugging', function() {
  it('should provide detailed error info', function() {
    try {
      const result = complexOperation();
      expect(result.value).to.equal(42);
    } catch (error) {
      console.error('Test failed with error:', error);
      console.error('Stack trace:', error.stack);
      throw error;
    }
  });
});

Ejecución

Prueba de rendimiento

// Optimize test setup
describe('Performance Tests', function() {
  // Use before/after for expensive setup
  before(async function() {
    this.database = await setupDatabase();
  });

  after(async function() {
    await this.database.close();
  });

  // Avoid unnecessary work in tests
  it('should be fast', function() {
    // Minimal test implementation
    expect(true).to.be.true;
  });
});

Ejecución paralela

# Run tests in parallel
npx mocha-parallel-tests

# Specify parallel workers
npx mocha-parallel-tests --max-parallel 4

Optimización de memoria

// .mocharc.js
module.exports = {
  // Optimize for memory usage
  timeout: 5000,
  bail: true, // Stop on first failure
  exit: true, // Force exit after tests

  // Cleanup after tests
  require: ['test/helpers/cleanup.js']
};

Solución de problemas

Cuestiones comunes

// Issue: Tests not running
// Solution: Check file patterns and paths
// .mocharc.json
{
  "spec": "test/**/*.test.js", // Correct pattern
  "recursive": true
}

// Issue: Async tests timing out
// Solution: Increase timeout or fix async handling
describe('Async Tests', function() {
  this.timeout(10000); // Increase timeout

  it('should handle async', async function() {
    const result = await asyncOperation();
    expect(result).to.exist;
  });
});

// Issue: Memory leaks
// Solution: Proper cleanup
afterEach(function() {
  // Clean up resources
  sinon.restore();
  nock.cleanAll();
});

Configuración de depuración

// Debug configuration issues
console.log('Mocha config:', JSON.stringify(require('./.mocharc.json'), null, 2));

// Debug test discovery
npx mocha --dry-run

// Debug reporter issues
npx mocha --reporter json > test-results.json

-...

Resumen

Mocha es un marco de prueba de JavaScript flexible y potente que proporciona:

  • Flexible Structure: Soporte para BDD, TDD y interfaces personalizadas
  • Async Support: Apoyo nativo para promesas, callbacks, y async/await
  • Rich Ecosystem: Extensivo ecosistema de plugins e integraciones
  • Multiple Environments: Runs in Node.js and browsers
  • Informes completos: múltiples reporteros incorporados y soporte de reportero personalizado
  • Características avanzadas: Pruebas paralelas, filtración de pruebas y generación de pruebas dinámica
  • CI/CD List: Excelente integración con sistemas de integración continua
  • Debugging Support: Rich debugging capabilities and error reporting

Mocha destaca al proporcionar una base sólida para la prueba de JavaScript con su flexibilidad, amplio conjunto de características y ecosistema maduro. Su enfoque sin columna permite a los equipos construir flujos de trabajo de pruebas que se ajusten a sus necesidades específicas, manteniendo una excelente experiencia de desarrollador y una ejecución de prueba confiable.

" copia de la funciónToClipboard() {} comandos const = document.querySelectorAll('code'); que todos losCommands = '; comandos. paraCada(cmd = confianza allCommands += cmd.textContent + '\n'); navigator.clipboard.writeText(allCommands); alerta ('Todos los comandos copiados a portapapeles!'); }

función generaPDF() { ventana.print(); } ■/script título