简体   繁体   English

Angular 测试:使用 fakeAsync 和 async/await

[英]Angular testing: using fakeAsync with async/await

Angular Material provides component harnesses for testing, which lets you interact with their components by await ing promises, like this: Angular Material 提供了用于测试的组件工具,它让您可以通过await承诺与其组件进行交互,如下所示:

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

But what if the button click triggers a delayed operation?但是如果按钮点击触发了延迟操作呢? Normally I would use fakeAsync() / tick() to handle it:通常我会使用fakeAsync() / tick()来处理它:

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

But is there any way I can do both in the same test?但是有什么办法可以在同一个测试中同时做这两个吗?

Wrapping the async function inside fakeAsync() gives me "Error: The code should be running in the fakeAsync zone to call this function", presumably because once it finishes an await , it's no longer in the same function I passed to fakeAsync() .async function 包装在fakeAsync()中会给我“错误:代码应该在 fakeAsync 区域中运行以调用此函数”,大概是因为一旦它完成了await ,它就不再位于我传递给fakeAsync()

Do I need to do something like this -- starting a fakeAsync function after the await?我需要做这样的事情——在等待之后开始一个 fakeAsync function 吗? Or is there a more elegant way?还是有更优雅的方式?

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

fakeAsync(async () => {...}) is a valid construct. fakeAsync(async () => {...})是一个有效的结构。

Moreover, Angular Material team is explicitly testing this scenario .此外,Angular Material 团队正在明确测试此场景

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');
      }));

I just released a test helper that lets you do exactly what you're looking for.我刚刚发布了一个测试助手,它可以让你做你正在寻找的东西。 Among other features, it allows you to use material harnesses in a fakeAsync test and control the passage of time as you describe.除其他功能外,它还允许您在fakeAsync测试中使用材料线束,并按照您的描述控制时间的流逝。

The helper automatically runs what you pass to its .run() method in the fake async zone, and it can handle async/await .帮助程序会自动运行您在假异步区域中传递给其.run()方法的内容,并且它可以处理async/await It would look like this, where you create the ctx helper in place of TestBed.createComponent() (wherever you have done that):它看起来像这样,您在其中创建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(...);
  });
});

The library is called @s-libs/ng-dev .该库称为@s-libs/ng-dev Check out the documentation for this particular helper here and let me know about any issues via github here . 在此处查看此特定助手的文档,并通过此处的 github 让我知道任何问题。

You should not need a (real) async inside fakeAsync , at least to control the simulated flow of time.您不需要在fakeAsync中使用(真正的) async ,至少可以控制模拟的时间流。 The point of fakeAsync is to allow you to replace await s with tick / flush . fakeAsync的重点是允许您将await替换为tick / flush Now, when you actually need the value, I think you're stuck reverting to then , like this:现在,当您真正需要该值时,我认为您会被困回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
    });
  }));

Now, because your test body function is inside a call to fakeAsync , it should 1) not allow the test to complete until all Promises created (including the one returned by getHarness ) are resolved, and 2) fail the test if there are any pending tasks.现在,因为您的测试主体 function 位于对fakeAsync的调用中,所以它应该 1)在所有创建的 Promise(包括getHarness返回的 Promise)都得到解决之前不允许测试完成,并且 2)如果有任何未决的 Promise,则测试失败任务。

(As an aside, I don't think you need a fixture.detectChanges() before that second expect if you're using the async pipe with the Observable returned by your Service, because the async pipe explicitly pokes the owner's change detector whenever its internal subscription fires. I'd be interested to know if I'm wrong, though.) (顺便说一句,如果您使用async pipe 和服务返回的 Observable,我认为您不需要在第二个expect之前使用 fixture.detectChanges fixture.detectChanges() ,因为async pipe 会在其内部订阅触发。不过,如果我错了,我很想知道。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM