Jasmine 2.0中的异步工作流测试

时间:2014-06-04 14:07:40

标签: javascript unit-testing jasmine

我有一个AngularJS应用程序,我需要测试工作流程并保证在广播事件后设置正确的值。

在1.3中我会这样做:

it('should have the correct match workflow', function() {
    // matchMaking event
    runs(function() {
        scope.$broadcast('matchMaking', gameId);
    });

    waitsFor(function() {
        return (scope.match && scope.match.game);
    }, 'A game should be defined', 3000);

    runs(function() {
        expect(scope.match.game).toBeDefined();
    });

    // matchCreate event
    runs(function() {
        scope.$broadcast('matchCreate', gameId, {}, {});
    });

    waitsFor(function() {
        return scope.match.status === 'CREATED';
    }, 'Match status should be \'CREATED\'', 3000);

    runs(function() {
        expect(scope.match.id).toBeDefined();
        expect(scope.match.player).toBeDefined();
        expect(scope.match.opponent).toBeDefined();
    });

    // matchPrepare event
    runs(function() {
        scope.$broadcast('matchPrepare');
    });

    waitsFor(function() {
        return scope.match.status === 'PREPARED';
    }, 'Match status should be \'PREPARED\'', 3000);

    runs(function() {
        expect(scope.match.id).toBeDefined();
    });

    // ... continues
});

使用Jasmine 2.0,似乎测试工作流的唯一解决方案是将setTimeout函数链接到彼此内(所有期望必须在同一规范内才能使用相同的范围):

beforeEach(inject(function($rootScope, $compile) {
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
    scope = $rootScope;
    element = angular.element('<pg-match-making></pg-match-making>');
    $compile(element)($rootScope);
    $rootScope.$digest();
}));

it('should have the correct match workflow', function(done) {
    var timeoutTick = 100;
    scope.$broadcast('matchMaking', gameId);
    setTimeout(function(){
        expect(scope.match.game).toBeDefined();

        scope.$broadcast('matchCreate', gameId, {}, {});
        setTimeout(function(){
            expect(scope.match.status).toEqual('CREATED');
            expect(scope.match.id).toBeDefined();
            expect(scope.match.player).toBeDefined();
            expect(scope.match.opponent).toBeDefined();

            scope.$broadcast('matchPrepare');
            setTimeout(function(){
                expect(scope.match.status).toEqual('PREPARED');
                expect(scope.match.id).toBeDefined();

                // ... call done() on the last setTimeout()
            }, timeoutTick);
        }, timeoutTick);
    }, 6000);
});

我最终得到了一堆7 setTimeout,这使得源代码更难阅读,测试速度非常慢。

使用Jasmine 2.0测试工作流程有没有更好的方法?

3 个答案:

答案 0 :(得分:1)

我有一个解决您问题的方法。我已经构建了一个小的简单异步测试框架,可以很好地与Jasmine 2.x一起使用,但它使用jQuery Deferred对象来安排延续。

function asyncWait(delay) {
    return new $.Deferred(function () {
        var _self = this;
        setTimeout(function () {
            _self.resolve();
        }, delay || 0);
    }).promise();
}

var Async = function(init) {
    var d = new $.Deferred(init);
    this.promise = d.promise();
    d.resolve();
};

Async.prototype.continueWith = function (continuation, delay) {
    var _self = this;
    _self.promise.then(function () {
        _self.promise = asyncWait(delay).then(continuation);
    });
    return _self;
};

Async.prototype.waitsFor = function (condition, timeout, pollInterval) {
    pollInterval = pollInterval || 10;
    timeout = timeout || 5000;
    var _self = this,
        wait_d = new $.Deferred(),
        t = 0,
        ln = function () {
            if (condition()) {
                wait_d.resolve();
                return;
            }
            if (t >= timeout) {
                wait_d.reject();
                throw "timeout was reached during waitsFor";
            }
            t += pollInterval;
            setTimeout(ln, pollInterval);
        };
    _self.promise.then(ln);
    _self.promise = wait_d.promise();
    return _self;
};

要使用此代码,请连接Jasmine测试并使用Async类的新实例

    it("some async test workflow I want to run", function (done) {
    new Async(function () {
        //wire up the first async call here
        var timeoutTick = 100;
        scope.$broadcast('matchMaking', gameId);
    }).continueWith(function () {
        expect(scope.match.game).toBeDefined();
        scope.$broadcast('matchCreate', gameId, {}, {})
    }, 6000).continueWith(function () {
        //more stuff here
    }).waitsFor(function () {
        // a latch function with timeout - maybe wait for DOM update or something
        return $(".my-statefull-element").val() === "updated";
    }, 1000).continueWith(done); //finish by waiting for done to be called
});

此代码不是100%傻瓜证明,但它适用于我。如果您有任何问题,请告诉我。

答案 1 :(得分:1)

使用一些额外的javascript,你可以使茉莉花的行为与1.3.1相似,而且你不需要引入任何额外的库。您只需要实现您缺少的轮询功能。这是一个简化的例子:

var value1 = false;
var value2 = false;
var value3 = false;

var test1 = function _test1() {
    setTimeout( function() { value1 = true; }, 1000 );
}

var test2 = function _test2() {
    setTimeout( function() { value2 = true; }, 5000 );
}

var test3 = function _test3() {
    setTimeout( function() { value3 = true; }, 300000 );
}   

var asyncCheckFn = function( done, waitFor, verify ) {
    if ( waitFor() ) {
        verify();
        done();
    } else {
        console.log( 'checking...' );
        setTimeout( function() { asyncCheckFn(done, waitFor, verify) }, 500);
    }   
};      

describe('async test suite', function() {

    it( 'works with short test', function( done ) {
        test1(); 
        asyncCheckFn( done, function() {
           return value1;
        }, function() {
           expect( value1 ).toBe( true );
        }); 
    }, 3000 );


    it( 'longer delay', function( done ) {
        test2();
        asyncCheckFn( done, function() {
           return value2;
        }, function() {
           expect( value2 ).toBe( true );
        });
    }, 10000 );


    it( 'fails', function( done ) {
        test3();
        asyncCheckFn( done, function() {
            return value3;
        }, function() {
           expect( value3 ).toBe( true );
        });
    }, 3000 );

});

asyncTestFn()执行与waitsFor()函数相同的任务 - 测试条件直到它为真。测试的总超时由传递给it()函数的最后一个参数控制。这是您的示例重写为线性测试而不是嵌套的setTimeouts:

describe('should have the correct match workflow', function() {

  var timerTick = 100;

  // matchMaking event
  it('defines the game', function(done) {
    scope.$broadcast('matchMaking', gameId);
    asyncCheckFn(done, function() {
      return scope.match && scope.match.game;
    }, function() {
      expect(scope.match.game).toBeDefined();
    });
  }, 6000);


  it('creates the match', function(done) {
    scope.$broadcast('matchCreate', gameId, {}, {});
    asyncCheckFn(done, function() {
      return scope.match.status === 'CREATED';
    }, function() {
      expect(scope.match.id).toBeDefined();
      expect(scope.match.player).toBeDefined();
      expect(scope.match.opponent).toBeDefined();
    });
  }, timerTick);


  it('prepares the match', function(done) {
    scope.$broadcast('matchPrepare');
    asyncCheckFn(done, function() {
      return scope.match.status === 'PREPARED';
    }, function() {
      expect(scope.match.id).toBeDefined();
    });
  }, timerTick);

  // ... continues
});

希望这会有所帮助。

(我知道这有点旧,但我在尝试解决类似问题时遇到了问题 - 如何嵌套顺序,依赖测试(答案,你不能......)。

(使用Jasmine 2.2测试的样品)

答案 2 :(得分:0)