Promise.catch() 不会在 AngularJS 单元测试中捕获异常

Posted

技术标签:

【中文标题】Promise.catch() 不会在 AngularJS 单元测试中捕获异常【英文标题】:Promise.catch() does not catch exception in AngularJS unit test 【发布时间】:2015-10-10 20:47:44 【问题描述】:

我正在使用 Typescript 为我的应用程序编写 Jasmine 单元测试,并通过 Resharper 运行它们。如果处理程序抛出异常,它应该执行一个动作:

describe("Q Service Test", () => 
    var q: ng.IQService;
    var rootScope: ng.IRootScopeService;

    beforeEach(inject(($q, $rootScope) => 
        q = $q;
        rootScope = $rootScope;
    ));

    it("Caught exceptions are handled properly", () => 
        var state = 'ok';
        q.when(1)
            .then(() => 
                throw new Error("test exception");
            )
            .catch(() => 
                state = 'error';
            );

        rootScope.$digest();
        expect(state).toBe('error');
    );
);

但是,测试失败了:

是我的测试环境/工具的一些奇怪行为,还是我错误地使用了承诺机制本身?

【问题讨论】:

【参考方案1】:

您肯定错误地使用了promise机制,抛出用户定义的throw语句并不构成正确地将其捕获为promise拒绝。如$q 文档中所述:

当将延迟/承诺与熟悉的行为进行比较时 try/catch/throw,将 reject 视为 javascript 中的 throw 关键字。 这也意味着,如果您通过 promise 错误“捕获”错误 回调,并且您希望将错误转发到派生自的承诺 当前的承诺,你必须通过返回一个“重新抛出”错误 通过拒绝构造拒绝。

它们相似但不等价,要捕获用户定义的 throw 语句,您应该使用 catch 语句块。而$q 承诺应该只catch 拒绝承诺。因此,在您的情况下,返回被拒绝的承诺是处理流程而不是抛出用户定义的异常的正确方法。

DEMO

JAVASCRIPT

describe('Q Service Test', function() 

  var $q,
      $rootScope;

  beforeEach(inject(function(_$q_, _$rootScope_) 
    $q = _$q_;
    $rootScope = _$rootScope_;
  ));

  it('Rejected promises are handled properly', function() 

    var state = 'ok';

    $q.when(1)
      .then(function() 
        return $q.reject('rejected');
      )
      .catch(function() 
        state = 'error';
      );

    $rootScope.$digest();
    expect(state).toBe('error');    

  );

);

更新:

您的代码在浏览器中以这种方式运行的原因是,Angular 的$q 实现在处理承诺队列时使用了try/catch 语句块。当任何回调抛出任何错误时,它会捕获错误本身,并以异常作为拒绝原因拒绝它,然后使用$exceptionHandler 记录错误。我建议你简单地返回被拒绝的承诺。

至于单元测试之所以会这样,是因为angular-mocks实现的$exceptionHandler与实际应用的$exceptionHandler不同。前者创建具有不同模式的提供程序,默认的 angular-mocks 实现使用rethrow 模式,该模式反过来抛出异常而不是记录它。如果您想让您的单元测试的行为方式与默认应用程序的$exceptionHandler 相同,那么您可以将模式设置为'log'

DEMO

JAVASCRIPT

describe('Q Service Test', function() 

  var $q,
      $rootScope;

  beforeEach(module('ng', function($exceptionHandlerProvider) 
    $exceptionHandlerProvider.mode('log');
  ));

  beforeEach(inject(function(_$q_, _$rootScope_) 
    $q = _$q_;
    $rootScope = _$rootScope_;
  ));

  it('Caught exceptions are handled properly', function() 

    var state = 'ok';

    $q.when(1)
      .then(function() 
        throw new Error();
      )
      .catch(function() 
        state = 'error';
      );

    $rootScope.$digest();
    expect(state).toBe('error');    

  );

);

【讨论】:

感谢您的详细解答。一个问题仍然存在 - 为什么我的带有 throw 的版本只能通过测试并在浏览器中按预期工作,但将异常传递到 .catch 块? 您已经提到它在浏览器中按预期工作,那么您能显示该部分的代码吗?【参考方案2】:

博文Using and chaining promises in AngularJS 提到当你抛出异常时,它“也会触发Angular 注册的异常处理程序”。所以我的猜测是 Jasmine 正在使用 Angular 的异常处理程序来监听异常。

你需要抛出一个异常还是你可以做这样的事情:

return q.reject(new Error("test exception"));

【讨论】:

带有显式q.reject 的版本按预期工作。然而,实际测试的模型与框架无关,不依赖于 Angular 服务,只依赖于 Promise 接口。我更喜欢没有这种依赖的解决方案。

以上是关于Promise.catch() 不会在 AngularJS 单元测试中捕获异常的主要内容,如果未能解决你的问题,请参考以下文章

在 promise.then 或 promise.catch 中调用时,Array.push 的工作很奇怪

重新抛出 promise catch 中的错误

Javascript:如何将其他变量传递给promise .catch函数

Promise Catch 在 json rpc 调用中未收到错误

如何重构一个使用相同函数的promise catch,但在调用函数中也是一个变量?

如何用 Mocha 测试 Promise catch