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

iva*_*rni 36 jasmine angularjs jasmine2.0

我知道在摘要周期中调用$digest$apply手动会导致"$ digest已在进行中"错误,但我不知道为什么我在这里得到它.

这是一个包装服务的单元测试$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');
            }
        };
    }]);
Run Code Online (Sandbox Code Playgroud)

单元测试也非常简单,它用于$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();
});
Run Code Online (Sandbox Code Playgroud)

done()随着"已经在进行中的$ digest"错误的调用,这会爆发.我不知道为什么.我可以通过包装这样done()的超时来解决这个问题

setTimeout(function() { done() }, 1);
Run Code Online (Sandbox Code Playgroud)

这意味着done()在$ digest完成后会排队并运行,但是这解决了我想知道的问题

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

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

dei*_*tch 74

$httpBacked.flush()实际上开始并完成一个$digest()循环.昨天我花了整整一天的时间挖掘ngResource和angular-mocks的源代码来深入了解它,但仍然没有完全理解它.

据我所知,目的$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();
});
Run Code Online (Sandbox Code Playgroud)

如果添加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");
});
Run Code Online (Sandbox Code Playgroud)

然后输出将是:

预取

冲洗前

异步回调开始

异步回调结束

后冲水

每次.如果您真的想看到它,请抓住范围并查看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);
});
Run Code Online (Sandbox Code Playgroud)

你会看到输出:

pre-get undefined

预冲洗未定义

异步回调开始$ digest

异步回调结束$ digest

后期未定义


Mat*_*cum 12

@deitch是对的,$httpBacked.flush()触发摘要.问题是,当$httpBackend.verifyNoOutstandingExpectation();每个it完成后运行时,它也有一个摘要.所以这是事件的顺序:

  1. 你打电话给flush()哪个触发摘要
  2. then()执行
  3. done()执行
  4. 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();
});
Run Code Online (Sandbox Code Playgroud)

done()在超时将使其立即执行当前的消化完成后().这将确保expects您想要运行的所有内容都将实际运行.

  • 您可以在afterEach中将verifyNoOutstandingExpectation()置于超时状态,以使您的'it'更清洁. (3认同)