Asynchronous Testing

The key to Async testing in FlexUnit is the use "async" metadata along with the Async classes asyncHandler method. Normally, FlexUnit tests run to completion, and are not concerned with waiting for timers or other events to take place. These two pieces take care of FlexUnit's impatience, and allows it to wait for the expected outcome at the expected time.

Here is an example of how to begin an async test case:

private var timer:Timer;

[Before]
public function setUp():void {
    timer = new Timer( 100, 1);
}

[After]
public function tearDown():void {
    if( timer )
        timer.stop()

    timer = null;
}

It's nice to start with a [Before] and [After] function, so that the time can be initialized (and removed) for every method in the case. This is another good example of why it is best to isolate test cases that include asynchronous tests. In other words, don't mix these test in with non-async tests that should be declared in other cases. Declare them in separate cases and put them together in a suite.

Here is an example of a single test within the case:

private var numberToTest:Number = 0;

[Test (async)]
public function shouldRunFastEnoughToPass():void {      
    timer.delay = 1000;
    timer.addEventListener( TimerEvent.TIMER_COMPLETE, 
        Async.asyncHandler(this, handleInTime, 2000, null, handleTimeout ), false, 0, true);
    timer.start();

    while (numberToTest < 1000)
        numberToTest++;
}

protected function handleInTime(event:TimerEvent, passThroughData:Object):void {
    Assert.assertEquals( 1000, numberToTest );
}

protected function handleTimeout( passThroughData:Object ):void {
    Assert.fail( "Async.asyncHandler timed out" );
}

Although this is fairly boring test, we do need it to be asynchronous, so that the while statement has enough time to run. If the timer reaches its delay (1000) before the numberToTest variable has reached 1000, the Assert.assertEquals statement in the handleInTime function will fail.

Here is another example, where (boringly) we use the passThroughData parameter in the asyncHandler method to pass some data to the handler.

private var numberToTest:Number = 0;
private var trueVariable:Boolean = true;

[Test (async)]
public function shouldRunFastEnoughToPass():void {      
    timer.delay = 1000;
    timer.addEventListener( TimerEvent.TIMER_COMPLETE, 
        Async.asyncHandler(this, handleInTime, 2000, trueVariable, handleTimeout ), false, 0, true);
    timer.start();

    while (numberToTest < 1000)
        numberToTest++;
}

protected function handleInTime(event:TimerEvent, passThroughData:Object):void {
    Assert.assertTrue( passThroughData );
    Assert.assertEquals( 1000, numberToTest );
}

protected function handleTimeout( passThroughData:Object ):void {
    if ( passThroughData )
        Assert.fail( "Async.asyncHandler timed out, but at least the trueVariable was true" );
    else
        Assert.fail( "Async.asyncHandler timed out, and the trueVariable was false!" );
}

As you can see, the trueVariable can be used as the passThroughData variable in both the handleInTime and the handleTimeout functions.

Async testing allows for verbose messaging and control in cases where tests pass and fail due to time. Granted, they take a bit longer to run.

The final version of the test case in the last example should read as follows:

public class asyncCase {
    private var timer:Timer;

    [Before]
    public function setUp():void {
        timer = new Timer( 100, 1);
    }

    [After]
    public function tearDown():void {
        if( timer )
            timer.stop()

        timer = null;
    }

    private var numberToTest:Number = 0;
    private var trueVariable:Boolean = true;

    [Test (async)]
    public function shouldRunFastEnoughToPass():void {      
        timer.delay = 1000;
        timer.addEventListener( TimerEvent.TIMER_COMPLETE, 
            Async.asyncHandler(this, handleInTime, 2000, trueVariable, handleTimeout ), false, 0, true);
        timer.start();

        while (numberToTest < 1000)
            numberToTest++;
    }

    protected function handleInTime(event:TimerEvent, passThroughData:Object):void {
        Assert.assertTrue( passThroughData );
        Assert.assertEquals( 1000, numberToTest );
    }

    protected function handleTimeout( passThroughData:Object ):void {
        if ( passThroughData )
            Assert.fail( "Async.asyncHandler timed out, but at least the trueVariable was true" );
        else
            Assert.fail( "Async.asyncHandler timed out, and the trueVariable was false!" );
    }
}