1

我最近将我的应用程序从 Angular 1.5 更新到 1.6.3 并开始围绕我编写的基于 Promise 的代码获取 Jasmine 单元测试失败(使用 PhantomJS):

可能未处理的拒绝:未定义抛出

环顾四周,我发现公认的解决方案是将 .then() 与 .catch() 块链接起来,以优雅地处理拒绝。

我已经为我正在测试的一个源文件执行了此操作,以证明这可以解决它所犯的错误。

但是,它现在发现了一个进一步的问题,即当我的代码中调用 promise 拒绝时,我正在测试的期望不再通过。

这是我要测试的功能(在添加所需的 catch 块之后)

public deleteSomething = (thing) => {
    return this.UserMessages.buildConfirmDialog().then(() => {
        this.someService.remove(thing)
            .then(() => {
                this.UserMessages.showToast('Something deleted');
            })
            .catch((error) => {
                //handle error
            });
    })
    .catch((error) => {
        //handle error
    });
}

这是测试:

var thing = {foo: 'bar'},
    deferredRemove,
    deferredConfirm,
    //Mock service below injected into controller later on before test are run
    UserMessages = {
        buildConfirmDialog: jasmine.createSpy('buildConfirmDialog').and.callFake(function() {
            deferredConfirm = $q.defer();
            return deferredConfirm.promise.catch(angular.noop);
        })
    };

//Inject and controller setup here...

    describe('When deleting something', function() {
        beforeEach(function() {
            deferredRemove = $q.defer();
            spyOn(someService, 'remove').and.returnValue(deferredRemove.promise.catch(angular.noop));
        });
        describe('and the user confirms the deletion', function() {
            beforeEach(function() {
                ctrl.deleteSomething(thing);
                deferredConfirm.resolve();
                deferredRemove.resolve();
                $rootScope.$apply();
            });
            it('should call remove on someService', function() {
                console.log('someService.remove.calls = ' + someService.remove.calls.count());
                expect(someService.remove).toHaveBeenCalled();
            });
        });
        describe('and the user cancels the deletion', function() {
            beforeEach(function() {
                someService.remove.calls.reset();
                vm.deleteSomething(thing);
                deferredConfirm.reject({});
                $rootScope.$apply();
            });
            it('should not call remove on someService', function() {
                console.log('someService.remove.calls = ' + someService.remove.calls.count());
                expect(someService.remove.calls.count()).toEqual(0);
            });
        });
    });

在升级到 1.6.3 之前我没有这些.catch(angular.noop)部件,并且遇到了一些建议这样做以使测试满意的帖子,这肯定有助于我克服测试运行中未处理的拒绝错误。

我现在面临的问题是,对于拒绝测试规范,我的服务中不应该调用删除函数,因此调用次数应该为零,但它一直显示为 1。我添加了该行重置我的测试中的调用以确保它不是以前的测试贡献(我知道调用是为了在测试之间重置)。

当我在 1.5 上时,这个测试运行得很好,所以这一定是我的代码\测试的编写方式不能很好地适应 1.6.x 中的变化

有人可以解释一下这里可能发生的事情吗?

谢谢

4

2 回答 2

5

在升级到 1.6.3 之前我没有这些.catch(angular.noop)部件,并且遇到了一些建议这样做以使测试愉快的帖子,这无疑有助于我克服测试运行中未处理的拒绝错误。

添加.catch(angular.noop)肯定会处理未处理的拒绝。

它将被拒绝的承诺转换为履行的承诺!!

您的测试正确失败,因为您破坏了代码。

有关更多信息,请参阅Catch 方法不适用于 $http 获取请求


AngularJS V1.6 对 $q 的更改

报告带有非拒绝回调的承诺

没有回调来处理拒绝的被拒绝的承诺会报告这个,$exceptionHandler以便可以将它们记录到控制台。

突破性变化

未处理的拒绝承诺将被记录到$exceptionHandler.

依赖于特定顺序或消息数量的测试$exceptionHandler 将需要处理被拒绝的承诺报告。

将抛出的错误视为常规拒绝

以前,promiseonFulfilledonRejected处理程序中抛出的错误的处理方式与常规拒绝略有不同:它们被传递给$exceptionHandler()(除了被转换为拒绝)。

这种行为的原因是未捕获的错误与常规拒绝不同,因为它可能是由例如编程错误引起的。在实践中,这对用户来说是令人困惑或不受欢迎的,因为本地承诺和任何其他流行的承诺库都没有将抛出的错误与常规拒绝区分开来。(注意:虽然这种行为不违反Promises/A+ 规范,但也没有规定。)

此提交通过跳过对 的调用消除了区别,$exceptionHandler()从而将抛出的错误视为常规拒绝。

注意: 除非明确关闭,否则可能未处理的拒绝仍将被捕获并传递给 $exceptionHandler(),因此由于编程错误而引发的错误以及未处理(使用后续onRejected处理程序)的错误不会被忽视。

有关更多信息,请参阅AngularJS 开发人员指南 - 从 V1.5 迁移到 V1.6

于 2017-03-11T16:20:06.387 回答
4

通过此配置禁用可能未处理的拒绝并再次测试。

app.config(['$qProvider', function ($qProvider) {
    $qProvider.errorOnUnhandledRejections(false);
}]);
于 2017-03-11T10:27:08.920 回答