es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

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?

Tags:  , , , ,

Answer

  1. Avatar for davy.ai

    Para probar el método doWork de la clase Subject, una forma de hacerlo es usar la función jest.useFakeTimers() para simular el paso del tiempo durante las llamadas al setInterval. Esto permitiría que la prueba determine si el estado del objeto Subject cambia como se espera a lo largo del tiempo. Además, la función mockImplementation podría usarse para espiar a process.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:

    import Subject from "../src/subject.class";
    
    describe("conjunto de pruebas de subject", () => {
      beforeEach(() => {
        jest.useFakeTimers(); // Usa temporizadores falsos para simular las llamadas al setInterval
      });
    
      afterEach(() => {
        jest.runOnlyPendingTimers(); // Ejecuta cualquier temporizador pendiente después de cada prueba
        jest.useRealTimers(); // Restaura los temporizadores reales para la próxima prueba
      });
    
      it("debería salir en caso de entrada no válida", (done) => {
        const spyExit = jest
          .spyOn(process, "exit")
          .mockImplementation(((nu: number) => {
            expect(nu).toEqual(100);
            spyExit.mockRestore();
            done();
          }) as any);
    
        const subj = new Subject();
        subj.doWork("");
    
        // Simula el paso de 10 segundos (1000ms * 10), provocando la salida debido a una entrada no válida
        jest.advanceTimersByTime(10000);
      });
    
      it("debería salir después de 1000 iteraciones", (done) => {
        const spyExit = jest
          .spyOn(process, "exit")
          .mockImplementation(((nu: number) => {
            expect(nu).toEqual(0);
            spyExit.mockRestore();
            done();
          }) as any);
    
        const subj = new Subject();
        subj.doWork("valido");
    
        // Simula 1000 iteraciones de las llamadas al setInterval
        jest.advanceTimersByTime(1000);
    
        // En este punto, debería haberse desencadenado la salida
        // Agregamos un segundo adicional para permitir que se completen todas las llamadas
        jest.advanceTimersByTime(1000);
      });
    });
    

    En el primer caso de prueba, creamos un objeto Subject y llamamos a su método doWork con una cadena vacía, lo que debería provocar una salida inmediata con un código de salida de 100. La función jest.useFakeTimers() se llama en el gancho beforeEach para habilitar el uso de temporizadores falsos para esta prueba. Luego creamos un espía en process.exit y asignamos una implementación falsa que verifica que el código de salida sea 100 y llama al callback done proporcionado por Jest. Finalmente, simulamos el paso de 10 segundos usando la función jest.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 usando jest.runOnlyPendingTimers() y restauramos los temporizadores reales usando jest.useRealTimers() en el gancho afterEach, 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étodo doWork con una entrada válida. Esta vez, queremos simular 1000 iteraciones del callback del setInterval antes de que se desencadene la salida. Una vez más creamos un espía en process.exit, esta vez con un código de salida esperado de 0 y simulamos el paso del tiempo usando jest.advanceTimersByTime(). También agregamos un segundo adicional para garantizar que se completen todas las llamadas antes de llamar a done.

    Con estos cambios, ambos casos de prueba deberían pasar y probar adecuadamente el método doWork de la clase Subject.

Comments are closed.