使用单例在无尽的 setInterval(,0) 中对 process.exit 的开玩笑测试调用

Posted

技术标签:

【中文标题】使用单例在无尽的 setInterval(,0) 中对 process.exit 的开玩笑测试调用【英文标题】:Jest test call to process.exit in endless setInterval(,0) using Singletons 【发布时间】:2022-01-06 22:58:36 【问题描述】:

原来的项目有一个类有一些长期运行的工作。这是在服务器上的进程中完成的,偶尔会让SIGINTs 停止。一旦发生这种情况,我想保持状态。这就是为什么我将工作放入setInterval(,0) 包装器中,以便事件循环获取中断。这在while(true)-loop 中不起作用。

现在我想测试一下主题是否正常工作 - 在这种情况下:验证输入。 可以在here 找到 MWE。

工作在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`);
  

我创建一个new Subject,设置中断处理程序并调用subj.doWork("peanut")方法在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");

所有工作文件。为了测试对process.exit 的调用,我在其上创建了一个jest.spyOn,它从it 中的tests/subject.test.ts 中调用done 函数

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();
  );
);

问题是,Jestdone 被第一个it 调用之前退出; Jest 然后在命令行上抱怨已尝试写入日志。当取消skip 第二种情况时,第一种情况有效,因为Jest 还活着。但是,expect.toEqual(0); 也永远不会被调用。这个0 可以是任何数字,但测试仍然没有失败(它只是打印到控制台,process.exit 以“0”调用,但行号错误)。

如何测试这个Subject的工作方法?

【问题讨论】:

【参考方案1】:

所以有几件事要说:

    因为process.exit() 不再退出(因为它被模拟了),所以必须清除间隔并且我们需要在调用process.exit() 时返回。 在 Jest 测试中,计时器可以/应该/需要被伪造。 (见Jest Documentation on Timer-Mocks) 根据测试设置(我在实际项目中使用singleton 模式),每个test/it 有一个测试文件有助于确保它们不会与单例实例冲突。似乎每个文件有一个线程(然后它们并行运行)。 试试jest --runInBand

subject.class.ts:


if (!inputdata) 
  return process.exit(100); // note 'return'

const id = setInterval(()=> // save interval-id
  this.counter++;
  if (this.counter > 10e2) 
    clearInterval(id);      // clear before exit'ing
    return process.exit(0); // note 'return'
  
  if (this.counter % 10e1 == 0) 
    console.log(this.counter);
  
, 0);

tests/subject.test.ts:


beforeAll(() => 
    jest.useFakeTimers();
);
afterAll(() => 
    jest.useRealTimers();
);


// and in the test then run the Timers
expect(() => new Subject().doWork("")).not.toThrow();
jest.runAllTimers();

【讨论】:

以上是关于使用单例在无尽的 setInterval(,0) 中对 process.exit 的开玩笑测试调用的主要内容,如果未能解决你的问题,请参考以下文章

单例在Swift 3中具有属性

设计模式----单例模式

设计模式 - 单例模式之多线程调试与破坏单例

Spring源码分析(十三)缓存中获取单例bean

車设计模式之单例

使用单例时的三种单例写法