javascript - Pruebas unitarias de stream de NodeJS con jest - problema de tiempo de espera
Dado el siguiente Stream de Transformación de NodeJS:
class ObjectToCSVTransform extends Transform {
private hasSetHeaders: boolean;
constructor() {
super({ objectMode: true });
this.hasSetHeaders = false;
}
static create(): ObjectToCSVTransform {
return new this();
}
_write(
object: Record<string, unkwnown>,
_encoding: BufferEncoding,
callback: TransformCallback
): void {
this.push(this.generateCSV(telemetry));
callback();
}
private generateCSV(object: Record<string, unkwnown>): string {
let csv = '';
if (!this.hasSetHeaders) {
csv += this.createCSVHeaders(object);
this.hasSetHeaders = true;
}
csv += this.createRecord(object);
return csv;
}
private createCSVHeaders(object: Record<string, unkwnown>): string {
return `${Object.keys(object)}\n`;
}
private createCSVRecord(object: Record<string, unkwnown>): string {
return `${Object.values(object)}\n`;
}
}
Implementé el siguiente caso de prueba (usando jest), para probar que dado un flujo de objetos planos del "mismo tipo", la salida esperada sea una representación CSV válida de ellos:
describe('object to csv stream', () => {
const items: Record<string, unknown> = [
{ foo: 1, bar: 2, baz: 3 },
{ foo: 10, bar: 20, baz: 30 },
{ foo: 100, bar: 200, baz: 300 },
];
it('should transform a list of items to csv', (done) => {
const expectedCsv = 'foo,bar,baz\n1,2,3\n10,20,30\n100,200,300\n';
let csv = '';
Readable.from(items)
.pipe(ObjectToCSVTransform.create())
.on('data', (csvResult) => {
csv += csvResult;
console.log(csvResult); // just for debugging purposes
})
.on('end', () => {
console.log('streaming ended'); // just for debugging purposes
expect(csv).toEqual(expectedCsv);
done();
});
});
});
Aparentemente, durante la implementación del caso de prueba quería ver cómo fallaba mi caso de prueba: el flujo parecía funcionar ya que registraba cada línea de resultado CSV esperada y también el mensaje 'streaming ended' al final una vez que el flujo terminaba, pero la ejecución de la prueba no se completó en absoluto entonces. Fue Jest quien realmente terminó la ejecución después de superar el tiempo de espera predeterminado:
lanzado: "Se superó el tiempo de espera de 5000 ms para una prueba. Use jest.setTimeout(newTimeout) para aumentar el valor del tiempo de espera, si esta es una prueba de larga duración."
¿Qué me estoy perdiendo aquí?
Ten en cuenta que actualmente estoy usando el callback de jest para notificar cuando la operación asíncrona ha terminado.
Respuesta #1
Hay algo que me faltaba en mi caso de prueba que no estaba manejando la asincronía correctamente en absoluto y, por lo tanto, estaba provocando que jest lanzara la Exceeded timeout exception:
- rodeando la afirmación con un try-catch y haciendo que se llame al callback
done()en caso de que la afirmación falle.
.on('end', () => {
try {
expect(csv).toEqual(expectedCsv);
done();
} catch (error) {
done(error);
}
});
Para más información/referencia, consulta la documentación de Jest: https://jestjs.io/docs/asynchronous#callbacks
Respuesta #2
El problema es que el valor devuelto de csv no coincide con el valor esperado. El valor devuelto real incluye un \n al final. Si agregas esto a tu cadena expectedCsv, tu prueba pasará.
El error de tiempo de espera es una falsa pista.
Si haces console.log(csv.replace(/\n/g, ''\\n'')) en la función 'end', verás el salto de línea extra al final de la cadena.
import { Readable, Transform, TransformCallback } from 'stream';
class ObjectToCSVTransform extends Transform {
private hasSetHeaders: boolean;
constructor() {
super({ objectMode: true });
this.hasSetHeaders = false;
}
static create(): ObjectToCSVTransform {
return new this();
}
// eslint-disable-next-line no-underscore-dangle
_write(
object: Record<string, unknown>,
_encoding: 'utf8',
callback: TransformCallback,
): void {
this.push(this.generateCSV(object));
callback();
}
private generateCSV(object: Record<string, unknown>): string {
let csv = '';
if (!this.hasSetHeaders) {
csv += this.createCSVHeaders(object);
this.hasSetHeaders = true;
}
csv += this.createCSVRecord(object);
return csv;
}
private createCSVHeaders(object: Record<string, unknown>): string {
return `${Object.keys(object)}\n`;
}
private createCSVRecord(object: Record<string, unknown>): string {
return `${Object.values(object)}\n`;
}
}
describe('object to csv stream', () => {
const items: Record<string, unknown>[] = [
{ foo: 1, bar: 2, baz: 3 },
{ foo: 10, bar: 20, baz: 30 },
{ foo: 100, bar: 200, baz: 300 },
];
it('should transform a list of items to csv', (done) => {
const expectedCsv = 'foo,bar,baz\n1,2,3\n10,20,30\n100,200,300\n';
let csv = '';
Readable.from(items)
.pipe(ObjectToCSVTransform.create())
.on('data', (csvResult) => {
csv += csvResult;
console.log(csvResult); // just for debugging purposes
})
.on('end', () => {
console.log('streaming ended'); // just for debugging purposes
console.log(csv.replace(/\n/g, '\\n'));
expect(csv).toEqual(expectedCsv);
done();
});
});
});
