todo
incluido

joel a. villarreal bertoldi

testing con node.js
sin dependencias externas

beerjscba#94

hola!

soy Joey.

dev lead en ID Plans

escribo código desde 1999

emperador del té en proceso

casado + padre de perruhija

hablemos de las cosas
que necesitamos para testear

#5:

paciencia.

2022

node.js 18 incluye la primera versión del módulo node:test, se backportea a node.js 16.17 (LTS)

2023

features de node:test comienzan a considerarse stable con node.js 20

2023

se comienza a incluir timers, suites, skip/todo/only

?

2024

node.js 22, se comienza a incluir module mocking

?

¡Pero entonces ya podemos despedirnos de ........... en Node.js!

– Torcuato, dev minimalista

SÍ*

*teniendo en cuenta que las features más avanzadas están bajo flags experimentales y podrían cambiar en cualquier momento, sin perjuicio de romper por completo cualquier integración que tengas con dichos módulos.

CLI

Programado

node --test
import dotenv from 'dotenv';
import { spec } from 'node:test/reporters';
import { run } from 'node:test';
import process from 'node:process';
import fs from 'node:fs';
import path from 'node:path';

const files = fs.readdirSync(
  path.resolve(__dirname),
  { recursive: true, encoding: 'utf8', },
).filter(
  file => file.endsWith('.spec.ts'),
).map(
  file => path.resolve(__dirname, file),
);

run({
  setup() { dotenv.config({ path: '.env' }); },
  files,
}).compose(spec).pipe(process.stdout);
import dotenv from 'dotenv';
import { spec } from 'node:test/reporters';
import { run } from 'node:test';
import process from 'node:process';
import fs from 'node:fs';
import path from 'node:path';

const files = fs.readdirSync(
  path.resolve(__dirname),
  { recursive: true, encoding: 'utf8', },
).filter(
  file => file.endsWith('.spec.ts'),
).map(
  file => path.resolve(__dirname, file),
);

run({
  setup() { dotenv.config({ path: '.env' }); },
  files,
}).compose(spec).pipe(process.stdout);

archivos

de test

a correr

 ejecución

con spec

reporter 

¡Ah, pero para TypeScript ya voy a tener que instalar alguna cosa! Es igual a ts-jest al final.

– Torcuato, dev comparatista

SÍ, pero*

*node.js ahora tiene algo llamado loaders**.

** import si node > 20.5.1

node --import tsx
npm i -D tsx

veamos cómo migramos

Expectations

node:test utiliza assert en vez de expect, por lo que se escribe en otro estilo.

1.

Mocks

node:test tiene un sistema de mocks muy similar al de otros testing frameworks.

2.

Algo más avanzado

Acá entran module mocking, timers, coverage y demás.

3.

jest expect

node assert

expect(
  value
).toBe(
  true
);
assert.ok(
  value
);
assert.strictEqual(
  value,
  true
);
expect(value).toBe(true)
assert.strictEqual(value, true)

Espero que     valor              sea     verdadero

Afirmo       la estricta igualdad entre     valor      y  verdadero

assert(condition, errorMessage);

# Negativos
doesNotMatch
doesNotReject
doesNotThrow
notDeepEqual
notDeepStrictEqual
notEqual
notStrictEqual

# Positivos
deepEqual 		(objetos, ==)
deepStrictEqual	(objetos, ===)
equal
strictEqual
ifError
match
ok
rejects
throws

node:assert

algunos de estos
métodos existen
desde node v0.1

import { 
  it, 
  describe 
} from 'node:test';

import assert from 'node:assert/strict';

import db, { sql } from '../../src';

describe('Connection', () => {
  it('should connect to the database', async () => {
    const results = await db.query(
      sql`SELECT 2 + 3 as result`
    );
    assert.deepEqual(results, [{ result: 5 }]);
  });
});

test con asserts

import { 
  it, 
  describe 
} from 'node:test';

import assert from 'node:assert/strict';

import db, { sql } from '../../src';

describe('Connection', () => {
  it('should connect to the database', /* .... */);
  it('should call onError when failing', async (ctx) => {
    const onError = ctx.mock.fn(); // CREATE MOCK
    
    const results = await db.query(
      sql`SE 2 + 3 as result`,
      { onError } // PASS MOCK
    );
    
    // EVAL MOCK
    assert.strictEqual(onError.mock.callCount(), 1);
  })
});

test con mocks

¡Ah, pero tengo que repetir código todo el tiempo! ¿Dónde están mis beforeEach, afterEach?

– Torcuato, dev estructuralista

son iguales

before(), beforeEach(), after(), afterEach() funcionan exactamente igual en node:test

Bueno pero ¿y mis snapshots? A que esos no los tenés.

– Torcuato, dev fotógrafo

test('snapshot sin serializador', (ctx) => {
  t.assert.snapshot({ 
    value1: 1, value2: 2 
  });
});

test('snapshot con serializador', (ctx) => {
  t.assert.snapshot({ 
    value3: 3, value4: 4 
  }, {
    serializers: [
      (value) => JSON.stringify(value)
    ]
  });
}); 

Stage 1 - Early Development

import { it, describe } from 'node:test';

import assert from 'node:assert/strict';

import db, { sql } from '../../src';

describe('Connection', () => {
  it('should log queries', async (ctx) => {
    const mockedLogFn = ctx.mock.fn();
	const mockedLogger = ctx.mock.module("logger", {
      namedExports: { log: mockedLog }
    })
    
    await db.query(sql`SELECT 2 + 3 as result`);
    
    // EVAL MOCK
    assert.strictEqual(mockedLogFn.log.callCount(), 1);
    const [call] = mockedLogFn.calls;
    assert.strictEqual(call.arguments, [
      'Executing query: SELECT 2 + 3 as result'
    ]);
  })
});

test con module mocking

(usa --experimental-test-module-mocks)

¿
  preguntas
                   ?

se admiten preguntas de sommelierie de té

beban agua, tomen sol y toquen pasto

~ negru

gracias!