在Jasmine 2.0的asynchronoustesting中获得“$摘要已经在进行中”
我知道,在摘要循环中调用$digest
或$apply
程序将导致“$摘要已在进行中”错误,但我不知道为什么我在这里得到它。
这是一个包装$http
的服务的unit testing,服务非常简单,它只是防止重复调用服务器,同时确保尝试执行调用的代码仍然获得所需的数据。
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'); } }; }]);
unit testing也非常简单,它使用$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);
这意味着done()
会在$ digest完成后排队并运行,但同时解决了我想知道的问题
- 为什么angular度在消化循环中首先?
- 为什么调用
done()
触发这个错误?
我有与Jasmine 1.3运行绿色完全相同的testing,这只发生在我升级到茉莉花2.0后,并重写testing使用新的asynchronous语法。
$httpBacked.flush()
实际上启动并完成一个$digest()
循环。 我昨天花了整天的时间挖掘ngResource和angular-mock的源头,深入了解了这一点,但仍然没有完全理解它。
据我所知, $httpBackend.flush()
的目的是完全避免上面的asynchronous结构。 换句话说, it('should do something',function(done){});
的语法it('should do something',function(done){});
和$httpBackend.flush()
不能很好地一起玩。 .flush()
目的是通过待处理的asynchronouscallback,然后返回。 这就像一个大包装done
你所有的asynchronouscallback。
所以,如果我理解正确(现在适用于我),正确的方法是使用$httpBackend.flush()
时删除done()
处理器:
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语句,你会发现所有的callback在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"); });
那么输出将是:
预取
冲洗前
asynchronouscallback开始
asynchronouscallback结束
后冲水
每次。 如果你真的想看到它,抓住范围,看看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); });
你会看到输出:
预先获取undefined
预冲洗undefined
asynchronouscallback开始$摘要
asynchronouscallback结束$摘要
后冲洗undefined
@deitch是正确的, $httpBacked.flush()
触发摘要。 问题是,当$httpBackend.verifyNoOutstandingExpectation();
在it
完成之后运行,它也有一个摘要。 所以这是事件的顺序:
- 你调用
flush()
触发一个摘要 -
then()
被执行 -
done()
被执行 - 运行
verifyNoOutstandingExpectation()
会触发一个摘要,但是你已经在一个,所以你得到一个错误。
done()
仍然很重要,因为我们需要知道then()
中的'expect'是否被执行。 如果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
将实际运行。
添加@ deitch的答案。 为了使testing更健壮,可以在callback之前添加一个间谍。 这应该保证你的callback实际上被调用。
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(); });