2015-07-21 65 views
10

我正在为Typescript中的应用程序编写Jasmine单元测试,并通过Resharper运行它们。据推测,如果处理程序抛出异常执行操作:Promise.catch()在AngularJS单元测试中没有发现异常

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

然而,测试失败:

​​

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

回答

13

你肯定使用的承诺机制错误,抛出一个用户定义的throw语句不构成捕它作为承诺拒绝妥善。由于$q文件中指出:

当比较deferreds /承诺 的try/catch /抛出熟悉的行为,认为拒绝在JavaScript中的关键字。 这也意味着,如果您通过承诺错误 回调“发现”错误,并且您希望将错误转发给当前承诺的承诺,则必须通过返回 通过返回 拒绝来“重新抛出”错误拒绝。

它们类似但不等同,为了捕获用户定义的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');  

    }); 

}); 

UPDATE:

为什么你的代码具有这样的浏览器,是因为角度的$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');  

    }); 

}); 
+0

感谢您的详细解答。还有一个问题 - 我的带'throw'的版本怎么只能通过测试,并且在浏览器中按预期工作,除了被传递到'.catch'块? – Impworks

+0

你已经提到它**在浏览器**中按预期工作,那么你可以显示该部分的代码? – ryeballar

+0

当然是:https://jsfiddle.net/eqf54zzm/ – Impworks

1

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

你需要抛出一个异常,你可能只是做这样的事情:

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

有明确'预期q.reject'作品版本。但是,实际测试的模型与框架无关,并且不依赖于Angular服务,仅在promise界面上。我宁愿没有这种依赖性的解决方案。 – Impworks