测试 `catch()` 错误处理程序时出现 `$digest already in progress` 错误
Getting a `$digest already in progress` error when testing a `catch()` error handler
这是我的测试:
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.<anonymous> (tests/test_base.js:61:21)
时,它指向以下行:mockBackend.verifyNoOutstandingExpectation();
上面代码的问题在于它在 catch
块内抛出。与其他 promise 实现相反,$q 中的抛出和拒绝不是一回事。
考虑到 $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
可以被额外侦测以进行测试。
这是我的测试:
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.<anonymous> (tests/test_base.js:61:21)
时,它指向以下行:mockBackend.verifyNoOutstandingExpectation();
上面代码的问题在于它在 catch
块内抛出。与其他 promise 实现相反,$q 中的抛出和拒绝不是一回事。
考虑到 $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
可以被额外侦测以进行测试。