Fa quatre mòduls que arrosseguem un npm test que falla a propòsit. Avui s'acaba. Instal·lem Mocha i Chai, configurem l'executor, arreglem l'script test del package.json i escrivim la bateria unitària del domini d'Escena Viva: Sessio, Esdeveniment, GestorDeVendes i —la promesa pendent des del mòdul 8— la matriu sencera de src/autoritzacio/politica.js recorreguda cel·la per cel·la.

Contingut

  1. Instal·lar la pila i arreglar l'script test
  2. Anatomia d'un fitxer de proves
  3. Els ganxos i el seu ordre d'execució exacte
  4. Enfocar i saltar proves
  5. Chai: estils i catàleg d'assercions
  6. Assercions sobre errors
  7. Proves asíncrones i timeouts
  8. La bateria unitària d'Escena Viva
  9. Fàbriques de dades de prova

Instal·lar la pila i arreglar l'script test

npm install --save-dev mocha chai@4

La versió importa: Chai 5 només publica ESM i no es carrega amb require(). Com que Escena Viva és CommonJS, fixem la 4; si veus "chai": "^5..." tindràs ERR_REQUIRE_ESM a la primera execució.

Les opcions de l'executor van a .mocharc.json, a l'arrel, perquè siguin visibles per a l'equip i per al servidor d'integració contínua:

{
  "spec": ["test/**/*.test.js"],
  "recursive": true,
  "timeout": 5000,
  "reporter": "spec",
  "forbidOnly": true,
  "exit": false
}
Opció Significat
spec Patró de fitxers; amb la convenció *.test.js no hi ha ambigüitat
recursive Baixa per test/unitat/ i test/integracio/
timeout Mil·lisegons màxims per prova (2000 per defecte)
reporter spec imprimeix la jerarquia describe/it de manera llegible
forbidOnly Fa fallar l'execució si troba un .only oblidat
exit false: no forcis la sortida. Si alguna cosa queda oberta, vull assabentar-me'n

L'última mereix comentari. Si Mocha acaba però el procés no mor, és perquè alguna cosa continua viva: una connexió a Mongo, un setInterval, un servidor escoltant. "exit": true amaga el problema; false t'obliga a tancar bé els recursos. A 09-06 veurem com diagnosticar què ha quedat obert.

I ara, per fi, l'script que fa des del mòdul 5 que falla. Al costat dels que ja existien (start, dev, llavor, lint, format, auditoria), hi afegim:

{ "test": "mocha",
  "test:unitat": "mocha test/unitat/**/*.test.js",
  "test:veure": "mocha --watch",
  "comprovar": "npm run lint && npm test" }

Ja no cal passar rutes: Mocha llegeix .mocharc.json. test:veure reexecuta en desar, que és la manera de treballar mentre desenvolupes. npm test respon ara 0 passing amb codi de sortida 0: el primer verd del projecte.

Anatomia d'un fitxer de proves

Mocha exposa globals (describe, it, before...) sense importar-les; Chai sí que s'importa.

// test/unitat/sessio.test.js
'use strict';

const { expect } = require('chai');
const { Sessio } = require('../../src/domini/sessio.js');

describe('Sessio', () => {
  describe('vendre()', () => {
    it('descompta les localitats venudes de l aforament disponible', () => {
      const sessio = new Sessio({           // preparar
        id: 'ses-001-1', esdevenimentId: 'evt-001', inici: '2026-10-17T20:00:00.000Z',
        aforament: 400, venudes: 100, preuCentims: 2500,
      });
      sessio.vendre(3);                     // actuar
      expect(sessio.lliures).to.equal(297); // comprovar
    });
  });
});

describe agrupa i s'imbrica lliurement: per convenció l'exterior anomena la unitat i els interiors el mètode o l'escenari. it és una prova: passa si la funció acaba sense llançar, falla si llança (i una asserció de Chai incomplerta llança). Regla d'estil: no facis servir funcions fletxa si necessites el this de Mocha (per exemple this.timeout(10000)), perquè les fletxes no tenen this propi.

Els ganxos i el seu ordre d'execució exacte

Ganxo Quan Per a què
before Una vegada, abans de totes les proves del bloc Connectar a la base de dades, recursos cars
beforeEach Abans de cada it del bloc i dels imbricats Recrear l'estat net de cada prova
afterEach Després de cada it Restaurar dobles, netejar dades
after Una vegada, al final del bloc Tancar connexions

Amb un describe exterior que té els quatre ganxos i la prova A, i un describe interior amb els seus quatre ganxos i les proves B i C, la traça real és exactament aquesta:

before exterior
beforeEach exterior  >> prova A  afterEach exterior
before interior
beforeEach exterior · beforeEach interior  >> prova B  afterEach interior · afterEach exterior
beforeEach exterior · beforeEach interior  >> prova C  afterEach interior · afterEach exterior
after interior · after exterior

Les tres regles que se'n dedueixen: els beforeEach s'executen de fora cap a dins i els afterEach de dins cap a fora, com una pila; el beforeEach exterior s'executa també per a les proves dels blocs interiors (font habitual del «per què es reinicia el meu objecte?»); i el before interior no s'executa fins a la primera prova d'aquell bloc.

Què posar a cadascun: a before, només allò car i immutable, mai estat que les proves hagin de mutar. A beforeEach, la preparació, perquè si dues proves comparteixen un objecte mutable tard o d'hora es trepitjaran. A afterEach, la neteja obligatòria (sinon.restore(), esborrar col·leccions, restaurar rellotges): Mocha l'executa encara que la prova falli, i per això és el lloc correcte. A after, tancar el que va obrir before, o el procés no acabarà.

Enfocar i saltar proves

it.only('descompta les localitats venudes', () => { /* ... */ });  // nomes aquesta
it.skip('agrupa diverses sessions en una comanda', () => { /* saltada */ });
it('emet recordatori 24h abans de la sessio');  // pendent: sense funcio

L'error clàssic amb .only és oblidar-se'l, pujar-lo i que CI passi en verd executant una sola prova. Per això hi hem posat forbidOnly: true: un .only oblidat trenca CI de manera sorollosa en comptes de mentir en silenci (en local es desactiva amb mocha --forbid-only=false).

Les proves pendents són una manera honesta de deixar escrit el comportament que falta: apareixen a l'informe, així que no s'obliden com un TODO. Existeix a més el salt condicional, before(function () { if (!process.env.MONGODB_URI_TEST) this.skip(); }), útil quan una prova requereix un servei que pot no estar-hi.

Chai: estils i catàleg d'assercions

Chai ofereix tres estils per dir el mateix: assert.equal(a, b), expect(a).to.equal(b) i a.should.equal(b). Farem servir expect perquè es llegeix gairebé com una frase, no modifica Object.prototype (a diferència de should, que peta amb null i undefined) i els seus missatges de fallada inclouen el valor esperat i l'obtingut.

Asserció Què comprova Exemple
to.equal(v) Igualtat estricta (===) expect(sessio.lliures).to.equal(297)
to.deep.equal(v) Igualtat estructural expect(comanda.linies).to.deep.equal([{ quantitat: 2 }])
to.include(v) Conté element, subcadena o subconjunt expect(codi).to.include('EV-2026-')
to.have.property(p, v) Existeix la propietat, opcionalment amb valor expect(cos.error).to.have.property('codi', 'AFORAMENT_INSUFICIENT')
to.have.lengthOf(n) Longitud d'array o cadena expect(esdeveniment.sessions).to.have.lengthOf(2)
to.be.true / to.be.false / to.be.null Booleà exacte i absència de valor expect(sessio.exhaurida).to.be.true
to.throw(Tipus, /regex/) La funció llança expect(() => sessio.vendre(0)).to.throw(ErrorDeValidacio)
to.be.closeTo(v, delta) Nombres amb tolerància expect(sessio.ocupacio).to.be.closeTo(0.25, 0.001)
to.be.an('array') / to.be.instanceOf(C) Tipus i instància expect(cataleg).to.be.an('array')

L'error número u: equal contra deep.equal

const obtingut = { id: 'evt-001', titol: 'Concierto de Otono' };

expect(obtingut).to.equal({ id: 'evt-001', titol: 'Concierto de Otono' });
// FALLA: son dos objectes diferents en memoria, === es false

expect(obtingut).to.deep.equal({ id: 'evt-001', titol: 'Concierto de Otono' });
// PASSA: compara estructura, clau per clau, recursivament

expect(obtingut).to.deep.include({ titol: 'Concierto de Otono' });
// Nomes les claus que importen: ideal amb camps volatils com creatEl

Regla mnemotècnica: equal per a primitius, deep.equal per a tot el que s'escrigui amb claus o claudàtors. Quan vegis expected { id: 'evt-001' } to equal { id: 'evt-001' } amb dos objectes aparentment idèntics, et falta el deep.

Assercions sobre errors

Amb la jerarquia del mòdul 6, comprovar només que «alguna cosa ha llançat» és insuficient: si vendre(0) llancés un TypeError intern en lloc de l'ErrorDeValidacio esperat, una prova fluixa passaria igualment. L'escala va de to.throw() a seques (fluixa), a to.throw(ErrorDeValidacio) (millor), a to.throw(ErrorDeValidacio, /enter/) (tipus i missatge).

Però el que de veritat viatja al client és el codi, del qual depèn l'estat HTTP a través d'ESTAT_PER_CODI. El patró de captura permet fer-hi assercions:

it('llanca ConflicteDEstat amb codi AFORAMENT_INSUFICIENT si no queden localitats', () => {
  const sessio = crearSessio({ aforament: 400, venudes: 398 });  // nomes 2 lliures
  let capturat = null;
  try {
    sessio.vendre(5);
    expect.fail('S esperava un ConflicteDEstat en vendre 5 entrades');
  } catch (error) { capturat = error; }

  expect(capturat).to.be.instanceOf(ConflicteDEstat);
  expect(capturat.codi).to.equal('AFORAMENT_INSUFICIENT');
  expect(capturat.detalls).to.deep.include({ lliures: 2, sollicitades: 5 });
});

L'expect.fail(...) és imprescindible: sense ell, si vendre(5) no llancés, el catch no s'executaria, capturat continuaria sent null i les assercions fallarien amb un missatge confús. Quan n'hi ha prou amb una propietat hi ha versió compacta: expect(() => sessio.vendre(5)).to.throw(ConflicteDEstat).with.property('codi', 'AFORAMENT_INSUFICIENT').

Proves asíncrones i timeouts

Mocha ofereix tres mecanismes: async/await a l'it (la forma correcta), retornar la promesa (equivalent) i el callback done, herència de l'era pre-promeses que continua sent la millor opció per a esdeveniments que s'emeten en el futur.

it('retorna l esdeveniment amb les seves sessions', async () => {
  const esdeveniment = await repositoriEsdeveniments.cercarPerId('evt-001');
  expect(esdeveniment.sessions).to.have.lengthOf(2);
});

Veuràs done en acció unes pàgines més avall, amb GestorDeVendes. Els seus paranys: si no el crides mai la prova mor per timeout amb un missatge genèric; si una asserció falla dins del callback pot no atribuir-se a la prova correcta; i no el barregis mai amb async (Mocha llança Resolution method is overspecified).

La fallada silenciosa: la promesa no esperada

// MALAMENT: l'it retorna undefined; l'assercio s'executa DESPRES que la prova ha acabat.
it('llanca si l esdeveniment no existeix', () => {
  (async () => {
    const esdeveniment = await repositoriEsdeveniments.cercarPerId('evt-999');
    expect(esdeveniment).to.be.null;
  })();
});

Mocha dona l'it per bo immediatament i l'asserció s'avalua en el buit: si falla, apareixerà com un unhandledRejection sense relació aparent amb la prova, o directament no apareixerà. Regla: si al cos d'un it apareix async o .then, l'it ha de ser async i ha d'esperar.

Per provar que una promesa es rebutja serveix el mateix try/catch amb expect.fail, o el complement chai-as-promised (npm i -D chai-as-promised@7 i chai.use(chaiComPromeses)), que afegeix await expect(promesa).to.be.rejectedWith(RecursNoTrobat). Compte amb l'await davant d'expect: sense ell tornes a tenir una promesa no esperada i la prova passa sempre. És el mateix parany disfressat.

Timeouts

El missatge Error: Timeout of 2000ms exceeded significa una de tres coses, per freqüència: t'has oblidat el done() o has retornat una promesa que no es resol mai; l'operació és realment lenta (base de dades, bcrypt amb cost 12); o hi ha un interblocatge esperant un esdeveniment que ningú no emet. S'ajusta per bloc (describe('...', function () { this.timeout(10000); })), per prova, o es desactiva amb this.timeout(0) mentre depures. No apugis el timeout global per tapar una prova lenta: convertiries un interblocatge de dos segons en una espera de trenta.

La bateria unitària d'Escena Viva

test/unitat/sessio.test.js

'use strict';

const { expect } = require('chai');
const { ErrorDeValidacio, ConflicteDEstat } = require('../../src/errors.js');
const { crearSessio } = require('../ajudes/fabriques.js');

describe('Sessio', () => {
  describe('vendre()', () => {
    it('descompta les localitats venudes de l aforament disponible', () => {
      const sessio = crearSessio({ aforament: 400, venudes: 100 });
      sessio.vendre(3);
      expect(sessio.lliures).to.equal(297);
    });

    it('llanca ErrorDeValidacio si la quantitat no es un enter positiu', () => {
      const sessio = crearSessio({ aforament: 400, venudes: 0 });
      [0, -2, 1.5, 'dos'].forEach((q) => expect(() => sessio.vendre(q))
        .to.throw(ErrorDeValidacio));
    });

    it('llanca ConflicteDEstat amb codi AFORAMENT_INSUFICIENT si no hi caben', () => {
      const sessio = crearSessio({ aforament: 400, venudes: 398 });
      expect(() => sessio.vendre(5)).to.throw(ConflicteDEstat)
        .with.property('codi', 'AFORAMENT_INSUFICIENT');
    });

    it('permet vendre exactament les ultimes localitats lliures', () => {
      const sessio = crearSessio({ aforament: 400, venudes: 397 });
      sessio.vendre(3);
      expect(sessio.lliures).to.equal(0);
      expect(sessio.exhaurida).to.be.true;
    });
  });
});

Hi faltarien les propietats derivades (ocupacio amb be.closeTo, preuEuros, exhaurida en fals), que segueixen el mateix motlle. El cas frontera —vendre exactament les últimes tres— és el <= contra < que un dia s'escriu malament i provoca una sobrevenda d'una entrada.

test/unitat/esdeveniment.test.js

Esdeveniment és un agregat, així que les seves proves van sobre les sumes i sobre la reconstrucció: aforamentTotal és 800 i venudesTotals 350 amb dues sessions de 400 i 100/250 venudes; exhaurit és false si una sola sessió encara té lloc; i Esdeveniment.desDeJSON({ id, titol, salaId, sessions }) retorna una instanceOf(Esdeveniment) les sessions de la qual són instàncies de Sessio amb el seu lliures ja calculat, a més de llançar si el JSON no porta identificador. És el mateix patró que a Sessio: escenari mínim, una operació, asserció sobre el valor observable.

test/unitat/politica.test.js — la matriu cel·la per cel·la

La prova promesa al mòdul 8. Com que tePermis, potGestionarEsdeveniment i potVeureComanda són funcions pures, recorrem la matriu completa amb taules de casos i bucles:

describe('politica d autoritzacio', () => {
  describe('tePermis()', () => {
    const CASOS = [
      ['assistent', PERMISOS.VEURE_CATALEG, true],
      ['assistent', PERMISOS.COMPRAR_ENTRADES, true],
      ['assistent', PERMISOS.GESTIONAR_ESDEVENIMENTS, false],
      ['assistent', PERMISOS.GESTIONAR_USUARIS, false],
      ['organitzador', PERMISOS.GESTIONAR_ESDEVENIMENTS, true],
      ['organitzador', PERMISOS.VEURE_INFORMES_SALA, true],
      ['organitzador', PERMISOS.GESTIONAR_USUARIS, false],
      ['administrador', PERMISOS.GESTIONAR_ESDEVENIMENTS, true],
      ['administrador', PERMISOS.GESTIONAR_USUARIS, true],
      ['administrador', PERMISOS.VEURE_INFORMES_SALA, true],
      ['taquiller', PERMISOS.VEURE_CATALEG, false],  // rol desconegut i sense rol:
      [undefined, PERMISOS.VEURE_CATALEG, false],    // per aqui es colen les bretxes
    ];

    CASOS.forEach(([rol, permis, esperat]) => {
      it(`${esperat ? 'concedeix' : 'denega'} ${permis} al rol ${rol}`, () => {
        expect(tePermis(rol, permis)).to.equal(esperat);
      });
    });
  });

  describe('potGestionarEsdeveniment()', () => {
    const esdeveniment = { id: 'evt-001', salaId: 'org-almendra' };  // del Teatro Almendra
    const PROPIETAT = [
      ['administrador sobre qualsevol esdeveniment', { rol: 'administrador', salaId: null }, true],
      ['organitzador sobre la seva propia sala', { rol: 'organitzador', salaId: 'org-almendra' }, true],
      ['organitzador sobre una altra sala', { rol: 'organitzador', salaId: 'org-boveda' }, false],
      ['assistent sobre qualsevol esdeveniment', { rol: 'assistent', salaId: null }, false],
      ['usuari nul', null, false],  // el cas que mes vegades s'oblida
    ];

    PROPIETAT.forEach(([cas, usuari, esperat]) => {
      it(`${esperat ? 'permet' : 'denega'} gestionar: ${cas}`, () => {
        expect(potGestionarEsdeveniment(usuari, esdeveniment)).to.equal(esperat);
      });
    });
  });

  describe('potVeureComanda()', () => {
    const comanda = { id: 'ped-001', usuariId: 'usr-001' };

    it('permet a l assistent veure la seva propia comanda', () => {
      expect(potVeureComanda({ id: 'usr-001', rol: 'assistent' }, comanda)).to.be.true;
    });

    it('denega a l assistent veure la comanda d un altre assistent', () => {
      expect(potVeureComanda({ id: 'usr-002', rol: 'assistent' }, comanda)).to.be.false;
    });
  });
});

Disset cel·les recorregudes amb dos bucles de tres línies, casos límit inclosos. La sortida de Mocha es llegeix com una especificació de seguretat, i si algú inverteix un if a potGestionarEsdeveniment, diverses proves es posen vermelles anomenant exactament el problema.

test/unitat/gestor-vendes.test.js

GestorDeVendes és un EventEmitter, així que provem que emet el que ha d'emetre:

it('emet venda-registrada amb la sessio i la quantitat venuda', (done) => {
  gestor.once('venda-registrada', (dada) => {
    expect(dada.sessioId).to.equal('ses-001-1');
    expect(dada.quantitat).to.equal(2);
    done();
  });
  gestor.registrarVenda(crearSessio({ id: 'ses-001-1', venudes: 100 }), 2);
});

it('no repeteix aforament-baix en vendes posteriors de la mateixa sessio', () => {
  const sessio = crearSessio({ aforament: 100, venudes: 100 - LLINDAR_AFORAMENT_BAIX - 1 });
  let vegades = 0;
  gestor.on('aforament-baix', () => { vegades += 1; });
  gestor.registrarVenda(sessio, 2);
  gestor.registrarVenda(sessio, 1);
  expect(vegades).to.equal(1);
});

Amb un beforeEach(() => { gestor = new GestorDeVendes(); }) al davant i una tercera prova anàloga per a sessio-exhaurida. Hi conviuen dues tècniques: done per a l'esdeveniment asíncron i recollir en una variable per als síncrons, que permet comptar quantes vegades s'ha emès. La segona és la mena de regla que ningú no recorda en refactoritzar sis mesos després.

Fàbriques de dades de prova

Repetir el constructor complet a cada prova és soroll: amaga quina dada és rellevant per a cada cas. La solució són fàbriques amb valors per defecte i sobreescriptura parcial:

// test/ajudes/fabriques.js
'use strict';

const { Sessio } = require('../../src/domini/sessio.js');
const { Esdeveniment } = require('../../src/domini/esdeveniment.js');

let comptador = 0;

function crearSessio(sobreescriptures = {}) {
  comptador += 1;
  return new Sessio({  // valors per defecte valids, tot sobreescrivible
    id: `ses-test-${comptador}`, esdevenimentId: 'evt-001',
    inici: '2026-10-17T20:00:00.000Z',
    aforament: 400, venudes: 0, preuCentims: 2500, ...sobreescriptures,
  });
}

// crearEsdeveniment fa el mateix amb el Concierto de Otono d'org-almendra,
// mapant les seves sessions amb crearSessio; crearUsuari retorna la Lucia,
// assistent, [email protected], salaId nul. Tot sobreescrivible.

module.exports = { crearSessio, crearEsdeveniment, crearUsuari };

Tres propietats fan bona una fàbrica: retorna sempre un objecte nou (res de constants compartides), els seus valors per defecte són vàlids (crearSessio() sense arguments produeix una sessió utilitzable) i la sobreescriptura fa visible el que és rellevant (crearSessio({ venudes: 398 }) diu sense soroll que aquesta prova tracta de l'aforament gairebé ple).

Executa ara npm test: 38 passing (52 ms). Aquesta és la velocitat de les proves unitàries sobre domini pur, i la raó que la base de la piràmide sigui tan ampla.

Errors Comuns i Consells

  • ERR_REQUIRE_ESM en importar Chai. Ets a Chai 5 amb CommonJS. Instal·la chai@4.
  • Fer servir equal amb objectes. La fallada mostra dos objectes idèntics i et torna boig: necessites deep.equal.
  • Resolution method is overspecified. Has posat done i async al mateix it. Tria'n un.
  • Un .only oblidat. CI passa en verd executant una prova. Activa forbidOnly.
  • this.timeout() en una funció fletxa. No funciona: fes servir function ().
  • Preparar a before en comptes de a beforeEach. La primera prova muta l'objecte i les següents el reben brut. Si dubtes, beforeEach.
  • Assercions que no s'executen. Tota prova amb try/catch necessita el seu expect.fail al camí feliç.
  • Consell: verifica cada prova nova trencant el codi a propòsit (canvia >= per > a vendre i comprova que es posa vermella), i llança un sol fitxer amb npx mocha test/unitat/sessio.test.js en comptes de tocar .only.

Exercicis

Exercici 1: preu d'una comanda

Escriu test/unitat/comanda.test.js amb quatre proves per a Comanda: que el total suma les línies en cèntims, que una comanda acabada de crear està en estat pendent, que no admet més de 6 entrades per sessió i que una comanda sense línies llança. Fes servir una fàbrica local.

Exercici 2: la cel·la que falta

A la taula CASOS hi falta PERMISOS.ANULLAR_COMANDA. Afegeix-lo per als tres rols amb aquesta regla: l'assistent pot anul·lar les seves comandes, l'organitzador no, l'administrador sí. Després escriu la prova de potCanviarRol que verifiqui que només l'administrador pot canviar rols i que ningú no es pot canviar el rol a si mateix.

Exercici 3: caçar el fals positiu

Explica per què aquesta prova passa sempre, fins i tot si cercarPerId retorna null, i reescriu-la:

it('troba l esdeveniment per id', () => {
  repositoriEsdeveniments.cercarPerId('evt-001').then((esdeveniment) => {
    expect(esdeveniment.titol).to.equal('Concierto de Otono');
  });
});

Solucions

Exercici 1. Amb una fàbrica local crearComanda(s) que per defecte porta una línia de 2 entrades a 2500 cèntims:

it('suma el total de totes les seves linies en centims', () => {
  const comanda = crearComanda({ linies: [
    { sessioId: 'ses-001-1', quantitat: 2, preuCentims: 2500 },
    { sessioId: 'ses-002-1', quantitat: 1, preuCentims: 1800 },
  ] });
  expect(comanda.totalCentims).to.equal(6800);
});

Les altres tres són directes: crearComanda().estat ha de ser 'pendent' amb pagatEl nul; una línia amb quantitat: 7 ha de llançar ErrorDeValidacio amb un missatge que inclogui el 6; i crearComanda({ linies: [] }) també ha de llançar.

Exercici 2. Tres files noves a CASOS (['assistent', PERMISOS.ANULLAR_COMANDA, true], ['organitzador', ..., false], ['administrador', ..., true]) i quatre proves de potCanviarRol amb admin, marta (organitzadora) i lucia (assistent):

expect(potCanviarRol(admin, lucia)).to.be.true;   // l'administrador si
expect(potCanviarRol(marta, lucia)).to.be.false;  // l'organitzador no
expect(potCanviarRol(lucia, marta)).to.be.false;  // l'assistent no
expect(potCanviarRol(admin, admin)).to.be.false;  // ningu sobre si mateix

L'última és la important: impedeix que l'últim administrador es degradi per error i deixi la plataforma sense ningú que pugui gestionar usuaris.

Exercici 3. L'it no és async i no retorna la promesa del .then, així que Mocha dona la prova per acabada abans que el .then s'executi; si l'asserció falla després, la fallada arriba òrfena. La versió correcta és async, amb await i un expect(esdeveniment).to.not.be.null previ que dona un missatge de fallada molt més clar que un Cannot read properties of null.

Conclusió

npm test està verd. Tenim Mocha configurat amb .mocharc.json, Chai amb l'estil expect, els ganxos amb el seu ordre entès de veritat, el catàleg d'assercions que es fa servir cada dia, la tècnica per comprovar que un error és l'error correcte amb el seu codi, les maneres de provar codi asíncron amb el seu parany de la promesa no esperada, i una bateria real que cobreix el domini i la matriu de permisos completa.

Però fixa't en el que totes aquestes proves comparteixen: cap de les seves unitats no depèn de res extern. Sessio no crida la xarxa, politica.js no consulta la base de dades, GestorDeVendes no mira el rellotge. Per això es proven en mil·lisegons i de manera determinista.

Tan bon punt sortim d'aquesta bombolla, el terreny canvia. canvi-divises.js crida una API amb fetch, generarCodiEntrada fa servir l'any actual, els tokens d'accés caduquen als 15 minuts i els repositoris obren connexions a Mongo. Cap d'aquestes unitats no es pot provar de manera determinista tal com està.

A la lliçó següent, Dobles de Prova amb Sinon, substituirem el rellotge, la xarxa i la base de dades per versions controlades: espies, stubs, mocks i rellotges falsos. I veurem també on és el límit, perquè simular massa produeix proves que només comproven les teves pròpies suposicions.

Curs de Node.js: De Principiant a Avançat

Mòdul 1: Introducció a Node.js

Mòdul 2: Conceptes Bàsics

Mòdul 3: Sistema de Fitxers i E/S

Mòdul 4: HTTP i Servidors Web

Mòdul 5: NPM i Gestió de Paquets

Mòdul 6: Framework Express.js

Mòdul 7: Bases de Dades i ORMs

Mòdul 8: Autenticació i Autorització

Mòdul 9: Proves i Depuració

Mòdul 10: Temes Avançats

Mòdul 11: Desplegament i DevOps

Mòdul 12: Projectes del Món Real

© Copyright 2026. Tots els drets reservats