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
- Instal·lar la pila i arreglar l'script
test - Anatomia d'un fitxer de proves
- Els ganxos i el seu ordre d'execució exacte
- Enfocar i saltar proves
- Chai: estils i catàleg d'assercions
- Assercions sobre errors
- Proves asíncrones i timeouts
- La bateria unitària d'Escena Viva
- Fàbriques de dades de prova
Instal·lar la pila i arreglar l'script test
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 exteriorLes 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 funcioL'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 creatElRegla 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_ESMen importar Chai. Ets a Chai 5 amb CommonJS. Instal·lachai@4.- Fer servir
equalamb objectes. La fallada mostra dos objectes idèntics i et torna boig: necessitesdeep.equal. Resolution method is overspecified. Has posatdoneiasyncal mateixit. Tria'n un.- Un
.onlyoblidat. CI passa en verd executant una prova. ActivaforbidOnly. this.timeout()en una funció fletxa. No funciona: fes servirfunction ().- Preparar a
beforeen comptes de abeforeEach. 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/catchnecessita el seuexpect.failal camí feliç. - Consell: verifica cada prova nova trencant el codi a propòsit (canvia
>=per>avendrei comprova que es posa vermella), i llança un sol fitxer ambnpx mocha test/unitat/sessio.test.jsen 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 mateixL'ú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
- Què és Node.js?
- Instal·lació i Configuració de l'Entorn
- El Teu Primer Programa en Node.js
- El REPL de Node.js
- JavaScript Modern per a Node.js
- El Projecte del Curs: la Plataforma Escena Viva
Mòdul 2: Conceptes Bàsics
- Arquitectura de Node.js
- El Bucle d'Esdeveniments (Event Loop)
- Callbacks i Programació Asíncrona
- Promeses i async/await
- Esdeveniments i EventEmitter
- Mòduls CommonJS i require()
- Mòduls ES i Interoperabilitat
Mòdul 3: Sistema de Fitxers i E/S
- Lectura i Escriptura de Fitxers
- El Mòdul fs a Fons
- Rutes Multiplataforma amb el Mòdul path
- Treballant amb Streams
- Streams de Transformació i pipeline
- Buffers i Dades Binàries
Mòdul 4: HTTP i Servidors Web
- Creant un Servidor HTTP Simple
- Gestió de Sol·licituds i Respostes
- Enrutament Manual
- Servint Fitxers Estàtics
- Rebent Dades: Cossos de Petició i JSON
- Consumint APIs Externes des de Node.js
Mòdul 5: NPM i Gestió de Paquets
- Introducció a NPM i package.json
- Instal·lació i Ús de Paquets
- Versionat Semàntic i package-lock
- Scripts d'npm i Automatització del Projecte
- Creació i Publicació de Paquets
- Seguretat i Manteniment de Dependències
Mòdul 6: Framework Express.js
- Introducció a Express.js
- Configuració d'una Aplicació Express
- Enrutament a Express
- Middleware
- Middleware de Tercers Essencials
- Validació de Dades d'Entrada
- Gestió d'Errors
Mòdul 7: Bases de Dades i ORMs
- Introducció a les Bases de Dades
- Usant MongoDB amb Mongoose
- Operacions CRUD
- Relacions, Poblat i Consultes Avançades
- Usant Bases de Dades SQL amb Sequelize
- Migracions, Transaccions i Dades de Prova
Mòdul 8: Autenticació i Autorització
- Introducció a l'Autenticació
- Registre d'Usuaris i Hash de Contrasenyes
- Sessions i Galetes amb Passport.js
- Autenticació amb JWT
- Control d'Accés Basat en Rols
- Bones Pràctiques de Seguretat en APIs
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb Mocha i Chai
- Dobles de Prova amb Sinon
- Proves d'Integració
- Cobertura i Automatització de les Proves
- Depuració d'Aplicacions Node.js
Mòdul 10: Temes Avançats
- El Mòdul Cluster
- Fils de Treball (Worker Threads)
- Memòria Cau i Cues de Treball amb Redis
- Optimització del Rendiment
- Construcció d'APIs RESTful
- GraphQL amb Node.js
Mòdul 11: Desplegament i DevOps
- Configuració i Variables d'Entorn
- Registre i Monitoratge en Producció
- Usant PM2 per a la Gestió de Processos
- Empaquetatge amb Docker
- Desplegant a Heroku i Altres PaaS
- Integració i Desplegament Continus
