【问题标题】:Getting a `$digest already in progress` error when testing a `catch()` error handler测试“catch()”错误处理程序时出现“$digest already in progress”错误
【发布时间】:2017-08-27 00:26:26
【问题描述】:

这是我的测试:

it('add.user() should POST to /users/, failure', function() {
    mockBackend.expectPOST("/users/", {username:'u', password: 'p', email: 'e', location: 'loc'}).respond(400, {msg: "bad request"});

    BaseService.add.user({username:'u', password: 'p', email: 'e', location: 'loc'});

    mockBackend.flush();
});

afterEach(function() {
    mockBackend.verifyNoOutstandingExpectation();
    mockBackend.verifyNoOutstandingRequest();
});

当我运行这个测试时,我得到了这个错误:

Chromium 53.0.2785 (Ubuntu 0.0.0) Factory: BaseService add.user() should POST to /users/, failure FAILED
    [object Object] thrown
    Error: [$rootScope:inprog] $digest already in progress
    http://errors.angularjs.org/1.3.15/$rootScope/inprog?p0=%24digest
        at /home/user/Documents/ebdjango/ebdjangoapp/static/js/angular.js:63:12
        at beginPhase (/home/user/Documents/ebdjango/ebdjangoapp/static/js/angular.js:14820:15)
        at Scope.$digest (/home/user/Documents/ebdjango/ebdjangoapp/static/js/angular.js:14262:9)
        at Function.$httpBackend.verifyNoOutstandingExpectation (node_modules/angular-mocks/angular-mocks.js:1557:38)
        at Object.<anonymous> (tests/test_base.js:61:21)

这是BaseService.add.user()

self.add = {
    user: function(user) {
    return $http.post("/users/", user)
        .then(function successHandler(response) {
            return $http.post("/custom-api-auth/login", user)
         }).then(function successHandler(response) {
             $window.location.href = "/";

    // if there are errors, rewrite the error messages
    }).catch(function rejectHandler(errorResponse) {
                     for (prop in errorResponse.data) {
                             if (prop == "email") {
                                 errorResponse.data[prop] = "Please enter a valid email address.";
                             } else if (prop == "username") {
                                 errorResponse.data[prop] = "Username can only contain alphanumeric characters and '.'";
                             } else if (prop == "password") {
                                 errorResponse.data[prop] = "Please enter a valid password";
                             }
                     }
         throw errorResponse;
};

如何防止出现$digest already in progress 错误?

编辑:如果我删除throw errorResponse;,测试工作但我需要throw errorResponse;,因为我需要在前端显示错误(另一个控制器负责..BaseService.add.user().catch()基本上重写了错误应该显示在前端)。

编辑 2:当错误消息显示 at Object.&lt;anonymous&gt; (tests/test_base.js:61:21) 时,它指向以下行:mockBackend.verifyNoOutstandingExpectation();

【问题讨论】:

  • 错误信息被截断。请始终完整地发布它们。发布的代码中没有任何内容会导致这种情况。该测试很可能与其他单元不够隔离。如果应用程序中有路由器,则应将其存根。顺便说一句,$window.location 本身很糟糕,它会搞砸下一个响应不返回错误的测试。
  • @estus 我编辑了帖子以显示完整的错误消息。另外,如果不是$window.location,我应该使用什么来设置URL? (测试时,我想将 URL 设置为任何特定的 URL,并验证成功 POST 后 URL 是否更改。在实际应用程序中 - 未测试时 - 我使用 $window.location 将用户重定向到不同的 URL)跨度>
  • 你能分享你的mockBackend吗?

标签: angularjs jasmine karma-runner karma-jasmine angular-mock


【解决方案1】:

上面代码的问题是它在catch 块内抛出。与其他 Promise 实现相反,$q 中的 throwing 和 rejecting 不是一回事。

考虑到 $q 承诺链是在摘要($httpBackend.flush() 这里)上同步执行的,在 catch 块内抛出将导致未捕获的错误,而不是拒绝承诺。它可能会阻止完成摘要并导致下一个摘要出现 $digest already in progress 错误。

所以通常$q.reject 应该用于promise 中的预期错误,而throw 应该只用于严重错误。应该是

return $q.reject(errorResponse);

建议使用Jasmine promise matchers进行测试:

expect(BaseService.add.user({ ... }).toBeRejectedWith({ ... });

否则必须通过更复杂的promise链进行测试:

var noError = new Error;

BaseService.add.user({ ... })
.then(() => $q.reject(noError))
.catch((err) => {
  expect(err).not.toBe(noError);
  expect(err).toEqual(...);
});

$rootScope.$digest();

位置更改应该在测试中存根,因为在测试中更改实际位置是最不需要的:

module({ $window: {
  location: jasmine.spyObj(['href'])
} })

并且最好在 Angular 应用程序中使用$location 服务而不是直接访问location,除非另有证明,否则$location.path('/...') 而不是location.href = '/...'

在测试中使用它已经是安全的了,尽管$location.path 可以被额外监视以进行测试。

【讨论】:

  • 仅供参考,我记得我为什么使用$window.location.href 而不是$location.path。这是因为我想完全刷新网页并且使用 $location.path 不会刷新(它只会更改 URL)。
  • 是的,$location.path 不这样做。通常刷新页面被认为是 SPA 中的黑客行为,表明设计存在问题。无论如何,通过模拟 $window 来测试这种情况是可行的,如帖子中所述(可测试性是 $window.location 优于 location global 的原因)。
  • 好的,谢谢(我需要在这个应用程序中刷新整页的原因是因为我遇到了这个问题:stackoverflow.com/questions/43218285/… - 以防你熟悉模板继承并有解决方案) .
  • 我明白了。我不熟悉 Django 模板,但是是的,如果它需要刷新,那就错了。那里的服务器端模板的目的是什么?是搜索引擎优化吗?考虑到 Angular 适合 SPA, 最好完全不被服务器端模板触及,页面内容应该完全由 Angular 生成。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-19
相关资源
最近更新 更多