如何检测fakeAsync()

时间:2018-09-10 10:11:46

标签: angular unit-testing jasmine race-condition zone.js

我为Angular应用编写了此测试:

it('should request confirmation before deleting & abort action if user declined', fakeAsync(() => {
    spyOn(appService, 'confirm').and.returnValue(of(false));
    spyOn(personService, 'delete').and.callThrough();
    component.deleteEntry(testPerson);
    //tick(); // Missing tick()!
    expect(personService.delete).not.toHaveBeenCalled();
}));

这是我正在测试的组件方法:

async deleteEntry(person: Person) {
    if (await this.appService.confirm().toPromise()) {
        return;
    }
    try {
        await this.personService.delete(person).toPromise();
    } catch(resp) {
        this.appService.errorMsgBox();
    }
}

({confirm()的目的是显示一个确认对话框,并根据用户输入返回可观察到的发射true / false

如果仔细看,我的组件函数有错误。检查!的结果时,我忘记了confirm()运算符。正确的代码是

    if (!await this.appService.confirm().toPromise()) {

但是,测试将通过。我不确定100%,但是我想它可以通过,因为最后的expect()语句在confirm()返回值之前执行检查。因此,是的,当然personService.delete()还没有被调用。如果我取消对tick()的注释,则测试将按预期工作并检测到错误。

现在,我希望fakeAsync()由于待处理的微任务而引发错误。令我惊讶的是,事实并非如此。测试通过了,没有任何错误或警告,尽管the docs say

  

如果函数结尾处有任何待处理的计时器,则会引发异常。

所以看来我们这里有一个竞争条件,即confirm()在返回fakeAsync()之前但在expect()之后已解决。如果可能的话,fakeAsync()如果不控制这些事情怎么办?

我和其他开发人员将来也可能会忘记tick()flushMicrotasks()。所以我想知道如何避免这种情况。我缺少可以插入afterEach()中的某种辅助函数吗?还是fakeAsync()的行为是Angular错误,即它应该引发异常?

编辑

在Stackblitz上查看我的问题的完整工作示例:https://stackblitz.com/edit/angular-cnmubr。请注意,如果要重新运行测试或更改某些内容后,必须单击内部浏览器视图(在编辑器旁边)的“刷新”按钮。自动重新加载功能将无法正常工作并引发错误。

我提交了issue,就像评论中建议的那样。

2 个答案:

答案 0 :(得分:0)

This issue显示,当前fakeAsync()不会为未决的微任务抛出异常,而只会为未决的计时器抛出异常。目前,似乎没有办法确保测试结束时没有待处理的微任务。但是,开发人员将检查这是否是一项功能。

答案 1 :(得分:0)

如果您的测试至少以相同的方式编写,则如果tick丢失/错误,则一个或另一个应失败。这不是完美的方法,但是您可以尝试将测试逻辑移到一个通用函数中,以便对它可以达到预期的目的有一定的信心:


function callDelete() {
    spyOn(personService, 'delete').and.callThrough();
    component.deleteEntry(testPerson);
    //tick(); // Missing tick()!
}

it('aborts delete if user declined', fakeAsync(() => {
    spyOn(appService, 'confirm').and.returnValue(of(false));
    callDelete();
    expect(personService.delete).not.toHaveBeenCalled();
}));

it('deletes if user accepted', fakeAsync(() => {
    spyOn(appService, 'confirm').and.returnValue(of(true));
    callDelete();
    expect(personService.delete).toHaveBeenCalled();
}));

现在,您的第二项测试将失败(因为expectawait解决之前就已经运行了)。通过调试此错误,您将注意到被测代码中的逻辑错误,进行修复,然后找到测试错误。或者,您将找到并修复测试错误,这将导致第一个测试失败,然后找到逻辑错误。唯一会遗漏的方法是,如果tick在一项测试中错了,但在另一项测试中错了(或者没有测试所有条件分支)。