我知道在摘要周期中手动调用$digest
或$apply
会导致正在进行的" $摘要"错误,但我不知道为什么我在这里得到它。
这是一个包装$http
的服务的单元测试,该服务很简单,它只是阻止对服务器进行重复调用,同时确保尝试执行调用的代码仍然获得预期的数据
angular.module('services')
.factory('httpService', ['$http', function($http) {
var pendingCalls = {};
var createKey = function(url, data, method) {
return method + url + JSON.stringify(data);
};
var send = function(url, data, method) {
var key = createKey(url, data, method);
if (pendingCalls[key]) {
return pendingCalls[key];
}
var promise = $http({
method: method,
url: url,
data: data
});
pendingCalls[key] = promise;
promise.then(function() {
delete pendingCalls[key];
});
return promise;
};
return {
post: function(url, data) {
return send(url, data, 'POST');
},
get: function(url, data) {
return send(url, data, 'GET');
},
_delete: function(url, data) {
return send(url, data, 'DELETE');
}
};
}]);
单元测试也非常简单,它使用$httpBackend
来预期请求。
it('does GET requests', function(done) {
$httpBackend.expectGET('/some/random/url').respond('The response');
service.get('/some/random/url').then(function(result) {
expect(result.data).toEqual('The response');
done();
});
$httpBackend.flush();
});
随着done()
调用已经正在进行的" $摘要"错误。我不知道为什么。我可以通过将done()
包含在像这样的超时
setTimeout(function() { done() }, 1);
这意味着{$ 1}}将在$ digest完成后排队并运行,但是当我解决了我的问题时我想知道
done()
会触发此错误?我有完全相同的测试运行绿色与Jasmine 1.3,这只发生在我升级到Jasmine 2.0并重写测试以使用新的异步语法。
答案 0 :(得分:73)
$httpBacked.flush()
实际上开始并完成$digest()
周期。我昨天花了一整天的时间深入研究ngResource和角度模拟的来源,以便深入了解这一点,但仍然没有完全理解它。
据我所知,$httpBackend.flush()
的目的是完全避免上面的异步结构。换句话说,it('should do something',function(done){});
和$httpBackend.flush()
的语法不能很好地协同工作。 .flush()
的目的是推送挂起的异步回调然后返回。它就像围绕所有异步回调的一个大done
包装器。
因此,如果我理解正确(现在它对我有用),正确的方法是在使用done()
时移除$httpBackend.flush()
处理器:
it('does GET requests', function() {
$httpBackend.expectGET('/some/random/url').respond('The response');
service.get('/some/random/url').then(function(result) {
expect(result.data).toEqual('The response');
});
$httpBackend.flush();
});
如果添加console.log语句,您会发现所有回调始终在flush()
周期内发生:
it('does GET requests', function() {
$httpBackend.expectGET('/some/random/url').respond('The response');
console.log("pre-get");
service.get('/some/random/url').then(function(result) {
console.log("async callback begin");
expect(result.data).toEqual('The response');
console.log("async callback end");
});
console.log("pre-flush");
$httpBackend.flush();
console.log("post-flush");
});
然后输出将是:
预GET
预冲洗
async回调开始
异步回调结束
后冲洗
每一次。如果您真的想看到它,请抓住范围并查看scope.$$phase
var scope;
beforeEach(function(){
inject(function($rootScope){
scope = $rootScope;
});
});
it('does GET requests', function() {
$httpBackend.expectGET('/some/random/url').respond('The response');
console.log("pre-get "+scope.$$phase);
service.get('/some/random/url').then(function(result) {
console.log("async callback begin "+scope.$$phase);
expect(result.data).toEqual('The response');
console.log("async callback end "+scope.$$phase);
});
console.log("pre-flush "+scope.$$phase);
$httpBackend.flush();
console.log("post-flush "+scope.$$phase);
});
你会看到输出:
pre-get undefined
预先冲洗未定义
异步回调开始$ digest
异步回调结束$ digest
后期冲洗未定义
答案 1 :(得分:12)
@deitch是对的,$httpBacked.flush()
会触发摘要。问题是,在每个$httpBackend.verifyNoOutstandingExpectation();
完成后运行it
时,它也会有一个摘要。所以这是事件的顺序:
flush()
来触发摘要then()
done()
verifyNoOutstandingExpectation()
会触发摘要,但您已经在其中,因此会收到错误。 done()
仍然很重要,因为我们需要知道'期望'在then()
内甚至被执行。如果then
没有运行,那么您现在可能知道有失败。关键是在解雇done()
之前确保摘要已完成。
it('does GET requests', function(done) {
$httpBackend.expectGET('/some/random/url').respond('The response');
service.get('/some/random/url').then(function(result) {
expect(result.data).toEqual('The response');
setTimeout(done, 0); // run the done() after the current $digest is complete.
});
$httpBackend.flush();
});
将done()
置于超时状态将使其在当前摘要完成后立即执行()。这将确保您想要运行的所有expects
实际运行。
答案 2 :(得分:1)
添加@deitch的回答。为了使测试更加强大,您可以在回调之前添加间谍。这应该保证你的回调实际上被调用。
it('does GET requests', function() {
var callback = jasmine.createSpy().and.callFake(function(result) {
expect(result.data).toEqual('The response');
});
$httpBackend.expectGET('/some/random/url').respond('The response');
service.get('/some/random/url').then(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalled();
});