【问题标题】:Sinon Fake Timer Call multiple Times In Test Only Firing OnceSinon Fake Timer 在测试中多次调用仅触发一次
【发布时间】:2026-01-03 14:15:02
【问题描述】:

我有一个递归调用自身的函数,直到它调用的函数完成。 这是正在测试的函数(我已经对其进行了修改,以便在 * 上发布,因为它是专有的。):

private async awaitQueryCompletion(queryId: string): Promise<void> {
    setTimeout(async () => {
      const output: ExecutionOutput =
        await this.getQueryExecution({ QueryExecutionId: queryId }).promise();

      let state: string | undefined;
      if (ExecutionOutput.QueryExecution !== undefined && ExecutionOutput.QueryExecution.Status) {
        state = ExecutionOutput.QueryExecution.Status.State;
      }

      if (state !== undefined && state === "RUNNING") {
        await this.awaitQueryCompletion(queryId);
      }
    }, this.RETRY_INTERVAL);
  }

这是我的测试: 设置:

beforeEach(() => {
    sandbox = createSandbox();
    getQueryExecutionStub = sandbox.stub(QueryClass, "getQueryExecution")
    timer = sandbox.useFakeTimers({ shouldAdvanceTime: true});
});
 it.only("Should call getQueryExecution twice with correct params", async () => {
      const INTERVAL: number = 5010;
      getQueryExecutionStub.onFirstCall()  
        .returns({
          promise: async (): Promise<ExecutionOutput> => {
            return Promise.resolve({
              QueryExecution: {
                Status: {
                  State: "RUNNING"
                }
              }
            });
          }
        });

      getQueryExecutionStub.onSecondCall()
        .returns({promise: async (): Promise<ExecutionOutput> => {
            return Promise.resolve({
              QueryExecution: {
                Status: {
                  State: "SUCCEEDED"
                }
              }
            });
          }
        });

      await selector.query(testInput);
      timer.tick(INTERVAL);
      timer.tick(INTERVAL);

      expect(getQueryExecutionStub.calledTwice).to.equal(true);
    });
  });

我想要的是 getQueryExecutionStub 被调用两次,所以我正在模拟 setTimeout 函数并试图表现得认为已经发生了两个超时周期。我让它运行一次超时,但我不知道如何让它再次运行。任何和所有的帮助将不胜感激!我查看了 lolex 文档:(https://github.com/sinonjs/lolex) 和 sinon fake timers 文档 (https://sinonjs.org/releases/v8.0.4/fake-timers/)。

【问题讨论】:

    标签: node.js typescript sinon


    【解决方案1】:

    因此,经过一番挖掘,我能够弄清楚这一点。

    private async awaitQueryCompletion(queryId: string, context: Context): Promise<void> {
    
        return new Promise(async (resolve: Function, reject: Function): Promise<void> => {
          // tslint:disable-next-line: no-inferred-empty-object-type
          this.timeout(async () => {
            try {
              log.debug("Checking query completion");
              const queryExecutionOutput: Athena.GetQueryExecutionOutput =
                await this.athenaClient.getQueryExecution({ QueryExecutionId: queryId }).promise();
    
              let state: string | undefined;
              if (queryExecutionOutput.QueryExecution !== undefined
                && queryExecutionOutput.QueryExecution.Status !== undefined) {
                state = queryExecutionOutput.QueryExecution.Status.State;
              }
    
              if (state !== undefined && state === "RUNNING") {
                if (context.getRemainingTimeInMillis() > this.TIMEOUT_PADDING_MS) {
                  await this.awaitQueryCompletion(queryId, context);
                  resolve();
                } else {
                  log.error(`Failure: Unable to complete query before lambda shut down...`);
                  reject();
                }
    
              } else if (state === "SUCCEEDED") {
                resolve();
              } else if (state === "FAILED") {
                throw new Error(state);
              } else {
                log.error("Unable to determine the state of the query...");
                reject();
              }
    
    
            } catch (e) {
              log.error(`${JSON.stringify(e)}`);
              return reject(e);
            }
          }, this.RETRY_INTERVAL_MS);
        });
      }
    

    我需要的是将我的函数包装在一个 Promise 中,并且在每个 tick 解析该 Promise 到下一个 tick 之后可能会触发。

    【讨论】: