ActionScript/FlexUnitで非同期テスト

FlexUnit4はActionScriptの開発環境であるところのFlash Builder組み込みのテストフレームワークなのでこれでテストを書きたいところだが、非同期コードのテストがなかなか厄介だったのでメモしておく。

まず、テストを開始する前に非同期で初期化処理(例えばログイン)をしたいのだが、これをFlexUnit runnerに伝える必要がある。これは、非同期ルーチンのコールバックをFlexUnitのAsync.asyncHandler()で包むことでできる。asyncHandler()はラッパー関数返し、イベントの発火が起きた時にこのラッパー関数が「次のメソッドに進んでよい」ことをFlexUnitに伝えてくれる。

ただし、Async.asyncHandler()の第一引数はTestCaseのインスタンス(ここではTestA)でなければならないので、グローバルな[BeforeClass(async)]は使えず、書くテストメソッド毎に呼ばれる[Before(async)]に書かなければならない。setUp()の冒頭でif(!initialized)とあるのは、何度も初期化コードが走るのを避けるためだ。

かくして非同期の初期化語に各々のテストメソッドが呼ばれるようになる。
個別の非同期テストケースも同様で、asyncHandler()でコールバックを包んでやればよい。

コードは以下のとおり:

package flexUnitTests
{
  import flash.events.TimerEvent;
  import flash.utils.Timer;
  import flash.utils.setTimeout;
  
  import org.flexunit.asserts.assertTrue;
  import org.flexunit.async.Async;

  public class TestA
  {
    private static const testTimeout :int = 5000;

    private static var initialized :Boolean = false;

    [Before(async)]
    public function setUp():void
    {
      trace('setup');
      if(!initialized) {
        var timer :Timer = new Timer(500);

        timer.addEventListener(TimerEvent.TIMER,
          Async.asyncHandler(this, function() :void {
            trace('in timer');
            initialized = true;
          }, testTimeout));;
        timer.start();
      }
    }

    [Test(async)]
    public function one() :void {
      trace('one');
      setTimeout( Async.asyncHandler(this, function() :void {
        trace('in one');
        assertTrue(initialized);
      }, testTimeout), 500);
    }
    [Test(async)]
    public function two() :void {
      trace('two');
      setTimeout( Async.asyncHandler(this, function() :void {
        trace('in two');
        assertTrue(initialized);
      }, testTimeout), 500);
    }
  }
}

実行時のログは以下の通り。ちゃんと一つ一つが終わってから次のメソッドへ行っている:

setup
in timer
one
in one
setup
two
in two

参考: