Angular - 测试组件 - 从模拟返回 Promise.reject 时出错

Posted

技术标签:

【中文标题】Angular - 测试组件 - 从模拟返回 Promise.reject 时出错【英文标题】:Angular - Testing component - Error when returning Promise.reject from mock 【发布时间】:2017-11-11 06:36:14 【问题描述】:

我有一个组件单元测试,它没有按我预期的方式处理来自模拟的承诺拒绝。

我在一个组件上有这个函数,它向addUserToOrganisation 发送一些数据并处理它返回的 Promise:

 public onSubmit() 
    this.saveStatus = 'Saving';
    this.user = this.prepareSaveUser();
    this._userService.addUserToOrganisation(this.user)
    .then(() => this._router.navigate(['/profile']))
    .catch(error => this.reportError(error));
  

在测试这个组件时,我为 UserService 提供了一个模拟,它监视 addUserToOrganisation 端点并返回某种 Promise:

 mockUserService = jasmine.createSpyObj('mockUserService', ['getOrgId', 'addUserToOrganisation']);
 mockUserService.getOrgId.and.returnValue(Promise.resolve('an id'));
 mockUserService.addUserToOrganisation.and.returnValue(Promise.resolve());

这适用于快乐路径(解决) - 我可以测试 this._router.navigate() 是否被调用,依此类推。这是这条幸福之路的通过测试:

it('should navigate to /profile if save is successful', fakeAsync(() => 
    fixture.detectChanges();
    tick();
    fixture.detectChanges();

    component.userForm.controls['firstName'].setValue('John');
    component.userForm.controls['lastName'].setValue('Doe');
    component.userForm.controls['email'].setValue('j.d@gmail.com');
    component.onSubmit();

    tick();
    fixture.detectChanges();
    expect(mockRouter.navigate).toHaveBeenCalledWith(['/profile']);
  ));

但是,我在测试“悲伤”路径时遇到了麻烦。我更改了我的模拟以返回一个 Promise.reject,虽然我在 onSubmit 中有一个 .catch,但我收到了这个错误:

Error: Uncaught (in promise): no

所以这很令人困惑。这是我对这条悲伤道路的测试。请注意,我更改了模拟调用的响应。

it('should show Failed save status if the save function fails', fakeAsync(() => 
    mockUserService.addUserToOrganisation.and.returnValue(Promise.reject('no'));
    fixture.detectChanges();
    tick();
    fixture.detectChanges();

    component.userForm.controls['firstName'].setValue('John');
    component.userForm.controls['lastName'].setValue('Doe');
    component.userForm.controls['email'].setValue('j.d@gmail.com');
    component.onSubmit();

    tick();
    fixture.detectChanges();

    expect(component.saveStatus).toEqual('Failed! no');
  ));

有人有什么想法吗?

【问题讨论】:

【参考方案1】:

Promise 拒绝应该被捕获,不处理它们将导致 Zone.js 的 promise 实现(用于 Angular 应用程序)和其他一些错误(Chrome、core-js promise polyfill 等)。

应同步捕获拒绝以被视为已处理。这样可以保证始终处理错误。

这是已处理的承诺:

const p = Promise.reject();
p.catch(err => console.error(err));

这是未处理的承诺:

const p = Promise.reject();
setTimeout(() => 
  p.catch(err => console.error(err));
, 1000);

即使将来可能会处理拒绝,但实现无法“知道”这一点,因此承诺被视为未处理并触发 unhandledrejection 事件。

造成问题的原因是

tick();
fixture.detectChanges();

如果tick() 存在“以防万一”并且没有真正使用它,那么它一开始就不应该存在。如果应该,则需要更改代码以不创建未处理的承诺:

mockUserService.addUserToOrganisation.and.callFake(() => Promise.reject('no'));

【讨论】:

我尝试删除tick(),但仍然出现错误。我不太明白这怎么会是不必要的?但是,将我的模拟响应从 returnValue... 更改为 callFake... 已经成功了。我不认为我 100% 理解这里的细微差别。 callFake 在调用它的那一刻创建一个被拒绝的承诺,因此它在component.onSubmit() 调用期间与.catch 链接并导致处理的承诺。 returnValue 接受现有的被拒绝承诺,该承诺应该立即与.catch 链接,但事实并非如此——因为在component.onSubmit() 中捕获拒绝之前,计时器会通过tick() 移动到下一个滴答声。 setTimeout 的示例准确地显示了那里发生的事情。它应该在没有tick() 的情况下工作。我不能说为什么没有它你仍然会得到这个错误。 @EstusFlask 它对我来说工作得很好,但它没有涵盖我代码中的 catch 块。我还希望通过覆盖 Promise.resolve 和 Promise.reject 来实现该方法 100% 的代码覆盖率 @PritamBohra 您的代码可能与发布的不同。我希望.and.callFake(() => Promise.reject('no')) 能够覆盖catch 完美运行。谢谢@EstusFlask

以上是关于Angular - 测试组件 - 从模拟返回 Promise.reject 时出错的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2 单元测试组件,模拟 ContentChildren

Angular - 组件中订阅功能的单元测试

如何在 Angular 单元测试中模拟/监视导入的函数

模拟指令以测试组件 - Angular 8 与 jasmine 和 Karma

Angular如何测试组件方法?

模拟服务以调用本地 json-server 而不是远程端点 - 测试语法? (角度 2)