无法等到 DOM 渲染在 Angular/Jasmine 单元测试中完成

Cannot wait until DOM rendering has finished in Angular/Jasmine unit test

我有一个通过 VegaEmbed (https://github.com/vega/vega-embed) 构建的 Angular 饼图组件,它使用 Vega 和 D3 作为底层图形依赖项。它通过提供标题和一些(键,值)对来呈现。我隔离了那个组件,并将 Stackblitz 中的 main.ts 修改为 运行 Jasmine 与您分享。在此测试中,我正在检查饼图是否确实呈现了值“30%”的 SVG <text> 标签 | “70%”和传说 "Combined CEO/Chair" | "Separate CEO/Chair"。然而,他们似乎 运行 太早了,VegaEmbed+Vega+D3 仍在忙于构建那个 SVG。 (我通过 Chrome 开发工具查看 DOM 来推断要测试的内容)。

https://stackblitz.com/edit/angular-d3-pie-chart-unit-test

我尝试了一系列的事情:asyncFakeAsync + tickjasmine.clock,改变我的 Angular 组件中的承诺逻辑,等等... fixture.whenStable 让我更近了一步,但是 texts 声明的第 50 行仍然未定义。

我不知道 Vega、VegaEmbed 和 D3 的内部结构是如何工作的。如果这些库没有使用承诺,而是 old-fashioned 回调,那么 Angular 的区域可能无法在 async ?

内等待足够的时间

让我有点困惑的是,console.log(texts); 最终在控制台中显示了一个 collection 的 4 个文本 SVG 元素。然而 console.log(texts.length); 显示 0!

  1. 怎么可能?
  2. 如何让我的测试代码等到 D3 完成绘制 SVG 的那一刻,然后只有 运行 expect 个语句?

这是一个很好的问题,我在 Ag-Grid 也有类似的问题,在我做断言之前我必须等待渲染或其回调完成并且没有像你提到的那样好的方法 fakeAsync, async/done, 等。至少 none 我找到了。

我发现的一种方法是制作一个实用函数,如下所示:

import { interval } from 'rxjs';
.....
export const waitUntil = async (untilTruthy: Function): Promise<boolean> => {
  while (!untilTruthy()) {
    // older API
    // await interval(25).pipe(take(1)).toPromise();
    // newer API
    await firstValueFrom(interval(25));
  }
  return Promise.resolve(true);
};

waitUntil 将每 25 毫秒循环一次,直到提供的回调函数为真。时间长短由你决定。

所以在你的测试中,你可以这样做:

it('should render the chart', async () => {
  // make your arrangements
  // do your action
  fixture.detectChanges();
  // wait for promises to resolve (optional)
  await fixture.whenStable();
  await waitUntil(() => /* put a condition here that will resolve to a truthy value 
  at a later time where the rest of the assertions rely on 
  it such as the graph being present with its labels*/);
  // the rest of your assertions of what should be there what should not
});

您提到 setTimeout 使用值 0。之所以可行,是因为我们将 setTimeout 中的内容放在调用堆栈队列的末尾,因为它是异步运行的。这样做仍然很好,但我喜欢使用 waitUntil 方法读取测试的方式。