【问题标题】:Angular testing: using fakeAsync with async/await角度测试:将 fakeAsync 与 async/await 结合使用
【发布时间】:2020-08-27 13:51:26
【问题描述】:

Angular Material 提供component harnesses 用于测试,它允许您通过awaiting 承诺与其组件进行交互,如下所示:

  it('should click button', async () => {
    const matButton = await loader.getHarness(MatButtonHarness);
    await matButton.click();
    expect(...);
  });

但是如果按钮点击触发延迟操作怎么办?通常我会使用fakeAsync()/tick() 来处理它:

  it('should click button', fakeAsync(() => {
    mockService.load.and.returnValue(of(mockResults).pipe(delay(1000)));
    // click button
    tick(1000);
    fixture.detectChanges();
    expect(...);
  }));

但是我有什么办法可以在同一个测试中同时做到这两个吗?

async 函数包装在fakeAsync() 中会给我“错误:代码应该在fakeAsync 区域中运行以调用此函数”,大概是因为一旦它完成await,它就不再在同一个函数中我传给fakeAsync()

我需要做这样的事情——在等待之后启动一个 fakeAsync 函数吗?还是有更优雅的方式?

  it('should click button', async () => {
    mockService.load.and.returnValue(of(mockResults).pipe(delay(1000)));
    const matButton = await loader.getHarness(MatButtonHarness);

    fakeAsync(async () => {
      // not awaiting click here, so I can tick() first
      const click = matButton.click(); 
      tick(1000);
      fixture.detectChanges();
      await click;
      expect(...);
    })();
  });

【问题讨论】:

    标签: angular unit-testing angular-material


    【解决方案1】:

    fakeAsync(async () => {...}) 是一个有效的构造。

    此外,Angular Material 团队是explicitly testing this scenario

    it('should wait for async operation to complete in fakeAsync test', fakeAsync(async () => {
            const asyncCounter = await harness.asyncCounter();
            expect(await asyncCounter.text()).toBe('5');
            await harness.increaseCounter(3);
            expect(await asyncCounter.text()).toBe('8');
          }));
    

    【讨论】:

    • 酷,谢谢。看起来这是在我发布几个月后修复的。
    【解决方案2】:

    我刚刚发布了一个测试助手,可以让您完全按照您的要求进行操作。除其他功能外,它还允许您在fakeAsync 测试中使用材料线束,并按照您的描述控制时间流逝。

    助手会自动运行您在伪异步区域中传递给其.run() 方法的内容,并且它可以处理async/await。它看起来像这样,您在其中创建 ctx 助手来代替 TestBed.createComponent()(无论您在哪里完成):

    it('should click button', () => {
      mockService.load.and.returnValue(of(mockResults).pipe(delay(1000)));
      ctx.run(async () => {
        const matButton = await ctx.getHarness(MatButtonHarness);
        await matButton.click();
        ctx.tick(1000);
        expect(...);
      });
    });
    

    该库名为@s-libs/ng-dev。查看此特定助手 here 的文档,并通过 github here 让我知道任何问题。

    【讨论】:

      【解决方案3】:

      你不应该在fakeAsync 中需要一个(真实的)async,至少可以控制模拟的时间流。 fakeAsync 的意义在于让您可以将awaits 替换为tick / flush。现在,当您真正需要该值时,我认为您无法恢复到 then,如下所示:

        it('should click button', fakeAsync(() => {
          mockService.load.and.returnValue(of(mockResults).pipe(delay(1000)));
          const resultThing = fixture.debugElement.query(By.css("div.result"));
          loader.getHarness(MatButtonHarness).then(matButton => {
            matButton.click(); 
            expect(resultThing.textContent).toBeFalsy(); // `Service#load` observable has not emitted yet
            tick(1000); // cause observable to emit
            expect(resultThing.textContent).toBe(mockResults); // Expect element content to be updated
          });
        }));
      

      现在,因为您的测试主体函数在对 fakeAsync 的调用中,所以它应该 1)在所有创建的 Promise(包括由 getHarness 返回的 Promise)都解决之前不允许测试完成,并且 2)失败测试是否有任何待处理的任务。

      (顺便说一句,如果您使用 async 管道和服务返回的 Observable,我认为您不需要在第二个 expect 之前使用 fixture.detectChanges(),因为 async 管道显式每当其内部订阅触发时,就会戳所有者的变更检测器。不过,我很想知道我是否错了。)

      【讨论】:

      • 为什么使用 then 优于 async/await?看起来很傻
      • fakeAsync 通过猴子修补全局 Promise 对象的运行时行为来工作,但浏览器中没有用于修改 async/await 语句行为的挂钩。这就是you can't emit native async functions without breaking Zone 的原因。在您的规范中使用 await 语句,最终在运行之前由您的转译器降级,但可能没问题。
      猜你喜欢
      • 2019-04-25
      • 1970-01-01
      • 2021-12-15
      • 2018-05-13
      • 1970-01-01
      • 2015-12-25
      • 2018-01-18
      • 2018-07-18
      • 2019-08-09
      相关资源
      最近更新 更多