使用Jasmine 2.0在异步测试中获取“$ digest已在进行中”

时间:2014-06-21 12:19:35

标签: angularjs jasmine jasmine2.0

我知道在摘要周期中手动调用$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完成后排队并运行,但是当我解决了我的问题时我想知道

  • 为什么Angular首先处于消化周期?
  • 为什么调用done()会触发此错误?

我有完全相同的测试运行绿色与Jasmine 1.3,这只发生在我升级到Jasmine 2.0并重写测试以使用新的异步语法。

3 个答案:

答案 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时,它也会有一个摘要。所以这是事件的顺序:

  1. 您拨打flush()来触发摘要
  2. 执行then()
  3. 执行done()
  4. 运行
  5. verifyNoOutstandingExpectation()会触发摘要,但您已经在其中,因此会收到错误。
  6. 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();
});