Llamada de prueba de Jest para process.exit en un setInterval infinito (,0) utilizando Singleton.
El proyecto original tiene una clase que realiza un trabajo de larga duración. Se hace en procesos en servidores, que ocasionalmente reciben señales SIGINT para detenerse. Quiero persistir el estado una vez que eso sucede. Es por eso que puse el trabajo en un envoltorio setInterval(,0), para que el bucle de eventos obtenga las interrupciones. Esto no funcionaría en un bucle while(true).
Ahora quiero probar si el sujeto funciona correctamente, en este caso: Validando la entrada. Se puede encontrar un ejemplo mínimo funcionando aquí.
El trabajo se realiza en subject.class.ts:
import { writeFileSync } from "fs";
import { tmpdir } from "os";
import { join, basename } from "path";
export default class Subject {
private counter = 0;
public doWork(inputdata: string): void {
if (!inputdata) {
process.exit(100); // exit if no input was given
}
setInterval(() => {
this.counter++;
if (this.counter > 10e2) {
process.exit(0);
}
if (this.counter % 10e1 == 0) {
console.log(this.counter);
}
}, 0);
}
public persist(): void {
const data = JSON.stringify(this.counter);
const path = join(tmpdir(), basename(__filename));
writeFileSync(path, data);
console.log(`Persisted to ${path}`);
}
}
Creo un nuevo sujeto, establezco los controladores de interrupción y llamo al método subj.doWork(“peanut”) para comenzar el trabajo en main.ts:
import Subject from "./subject.class";
const subj = new Subject();
process.on("exit", (exitcode: number) => {
if (exitcode == 0) {
process.stdout.write("\nDone Success. :)\n");
} else {
process.stderr.write(`\nDone with code: ${exitcode}\n`);
}
subj.persist();
process.stdout.write("exiting.");
process.exit(exitcode);
});
process.on("SIGINT", (signal: "SIGINT") => {
process.stdout.write(`\ncaught ${signal}`);
process.exit(13);
});
subj.doWork("peanut");
Todo eso funciona bien. Para probar la llamada a process.exit, creo un jest.spyOn en ella, que llama a la función done desde it en tests/subject.test.ts
import Subject from "../src/subject.class";
describe("subject test suite", () => {
jest.spyOn(console, "log").mockImplementation();
it("should exit on invalid input", (done) => {
const spyExit = jest.spyOn(process, "exit").mockImplementation(((
nu: number
) => {
expect(nu).toEqual(100);
spyExit.mockRestore();
done();
}) as any);
expect(() => new Subject().doWork("")).not.toThrow();
});
it.skip("should work on valid input", (done) => {
const spyExit = jest.spyOn(process, "exit").mockImplementation(((
nu: number
) => {
expect(nu).toEqual(0); // does not matter because it is not checked anyway
spyExit.mockRestore();
done();
}) as any);
expect(() => new Subject().doWork("valid")).not.toThrow();
});
});
El problema es que Jest sale antes de que se llame a done en el primer it; Jest luego se queja en la línea de comandos de que se han intentado escribir registros. Cuando se quita la restricción en el segundo caso, el primero funciona, porque Jest aún está vivo. Sin embargo, también nunca se llama la expectativa.toEqual(0). Este 0 puede ser cualquier número y la prueba aún no falla (simplemente imprime en la consola que process.exit se llamó con “0”, pero con un número de línea equivocado).
¿Cómo puedo probar el método de trabajo de este sujeto?
davy.ai
Para probar el método
doWork
de la claseSubject
, una forma de hacerlo es usar la funciónjest.useFakeTimers()
para simular el paso del tiempo durante las llamadas alsetInterval
. Esto permitiría que la prueba determine si el estado del objetoSubject
cambia como se espera a lo largo del tiempo. Además, la funciónmockImplementation
podría usarse para espiar aprocess.exit
y simular llamadas a él, lo que permitiría probar los diversos escenarios de salida. Aquí se muestra un ejemplo de cómo se podría hacer:En el primer caso de prueba, creamos un objeto
Subject
y llamamos a su métododoWork
con una cadena vacía, lo que debería provocar una salida inmediata con un código de salida de 100. La funciónjest.useFakeTimers()
se llama en el ganchobeforeEach
para habilitar el uso de temporizadores falsos para esta prueba. Luego creamos un espía enprocess.exit
y asignamos una implementación falsa que verifica que el código de salida sea 100 y llama al callbackdone
proporcionado por Jest. Finalmente, simulamos el paso de 10 segundos usando la funciónjest.advanceTimersByTime()
, lo que debería desencadenar la salida debido a la entrada no válida. También ejecutamos cualquier temporizador pendiente después del caso de prueba usandojest.runOnlyPendingTimers()
y restauramos los temporizadores reales usandojest.useRealTimers()
en el ganchoafterEach
, para asegurarnos de que otros casos de prueba no se vean afectados por el uso de temporizadores falsos.En el segundo caso de prueba, creamos un objeto
Subject
y llamamos a su métododoWork
con una entrada válida. Esta vez, queremos simular 1000 iteraciones del callback delsetInterval
antes de que se desencadene la salida. Una vez más creamos un espía enprocess.exit
, esta vez con un código de salida esperado de 0 y simulamos el paso del tiempo usandojest.advanceTimersByTime()
. También agregamos un segundo adicional para garantizar que se completen todas las llamadas antes de llamar adone
.Con estos cambios, ambos casos de prueba deberían pasar y probar adecuadamente el método
doWork
de la claseSubject
.