Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Exploring asynchronous procedures

Assumptions

This entry assumes you have followed the steps so far to build your first test suite and case. If you have not, feel free to read on, but a clearer picture can be obtained by reviewing this from the beginning.

Asynchronous Test Defined

An asynchronous test is a test that depends on some action that may not happen synchronously. An excellent example of this is an HTTPService in Flex. You make a call to the server to get data and, when the data is available, you are notified via an event. The original method which made that server call may have completed long ago.
Many of the common properties set on UIComponents in Flex are actually asynchronous. When setting a property on a given control, often the intended value is stored in a private variable and applied later during the commitProperties() method. Inherently this means we need asynchronous capability to truly test these features.

Approach

In previous versions of FlexUnit it was difficult to have multiple asynchronous events and to test code that was event driven but not always asynchronous. Fluint provides enhanced asynchronous support including asynchronous setup and teardown, but every test carried the overhead of the asynchronous code to facilitate this feature. FlexUnit 4 allows the developer to specify which tests need asynchronous support using the async parameter. When provided, the async parameter enables the full asynchronous support provided by Fluint for that particular test. When the async parameter is specified you may also specify an optional timeout for the method.

Create a New Test Case

  • Inside of your test runner project, browse to the sampleSuite/tests directory (the location where you previously created your TestCase1.as).
  • Right click select New -> Actionscript Class named TestAsync.as (if you're using FlashBuilder 4, select Test Case Class).
  • Import the Timer and the TimerEvent classes. Also import the Async and Assert classes.
    Code Block
    actionscript
    actionscript
         import flash.utils.Timer
         import flash.events.TimerEvent;
    
         import org.flexunit.async.Async;
         import org.flexunit.Assert;
    
  • Inside the class definition for TestAsync, create a new private variable named timer of type Timer.
    Code Block
    actionscript
    actionscript
         private var timer:Timer;
    
  • Your TestCase will need a public method named setUp(). This method will be called before each of your test methods. Your function should look like this:
    Code Block
    actionscript
    actionscript
         [Before]
         public function setUp():void {
    		
         }
    
  • Inside the setUp() method, instantiate the timer variable as illustrated in the following code:
    Code Block
    actionscript
    actionscript
         [Before]
         public function setUp():void {
              timer = new Timer( 100, 1 );			
         }
    

:This creates a new Timer that will complete 100 milliseconds after it is started and only triggers once.

  • Your TestCase will need a public method named tearDown(). This function will be used to stop the timer and set the timer reference to null when the test is complete.
    Code Block
    actionscript
    actionscript
        [After]
         public function tearDown():void {
              if( timer ) {
                   timer.stop();
              }
    
              timer = null;
         }
    
  • Now, create a new public test method called timerLongWay().
    Code Block
    actionscript
    actionscript
         [Test(async, description="Async Example")]
         public function timerLongWay():void {
    
         }
    

:In this method, we are going to setup our asynchronous handlers the long way to clearly illustrate the steps. In future methods, we are going to take an abbreviated approach. Specifying (async) notifies FlexUnit 4 that this is an asynchronous test.

  • As the first line of your timerLongWay() method, add this code which creates a new asynchronous handler.
    Code Block
    actionscript
    actionscript
         var asyncHandler:Function = Async.asyncHandler( this, handleTimerComplete, 500, null, handleTimeout );
    

:This code calls a method of the Async class called asyncHandler() to create a special class (the AsyncHandler class) that will monitor your test for an asynchronous event. The first parameter of this method is our listener class, namely the class in which the test is contained. The second parameter of this method is the event handler to call if everything works as planned (we received our asynchronous event). The third parameter is the number of milliseconds we are willing to wait, in this case 500. The fourth parameter is a generic object for passing additional data called passThroughData; we will deal with that shortly. The last parameter is the timeout handler. This is the method that will be called if the timeout (500ms) is reached before our asynchronous handler is called.

  • As the next line in our function, we will add an event listener to the timer instance for a TIMER_COMPLETE event. When this event occurs, we will call the asyncHandler function we defined on the line above.
    Code Block
    actionscript
    actionscript
         timer.addEventListener(TimerEvent.TIMER_COMPLETE, asyncHandler, false, 0, true );			
    

:Note: This event listener is using weak references. If you don’t feel comfortable with that concept, there are a lot of great articles written on the topic. I strongly advise you to take a look.

  • On the next line, add the code to start the timer. Your completed method should look like this:
    Code Block
    actionscript
    actionscript
         [Test(async, description="Async Example")]
         public function testTimerLongWay():void {
              var asyncHandler:Function = Async.asyncHandler( this, handleTimerComplete, 500, null, handleTimeout );
              timer.addEventListener(TimerEvent.TIMER_COMPLETE, asyncHandler, false, 0, true );
              timer.start();			
         }
    

:Following this procedure, we have let the TestCase code know that it should wait for either the TIMER_COMPLETE event or the 500ms timeout to occur before attempting to decide if the test case was a success or failure. Without this call to asyncHandler, this particular test method would be marked a success as soon as the method body finished executing.

  • In our event handler, the call to asyncHandler, we have referenced two methods that do not yet exist, handleTimerComplete and handleTimeout. Define those now as:
    Code Block
    actionscript
    actionscript
         protected function handleTimerComplete( event:TimerEvent, passThroughData:Object ):void {
    			
         }
    
         protected function handleTimeout( passThroughData:Object ):void {
    			
         }
    

:Note that the handleTimerComplete() method takes two parameters, an event object and an object called passThroughData. The handleTimeout() method only has a single parameter, the passThroughData. The passThroughData is the same data passed to the asyncHandler. This is a good way to pass through a test description, or check a state when the handler completes or times out.

  • We will leave the body of the handleTimerComplete() method empty for now, but add a fail() method call to the handleTimeout() method so that it looks like the following code
    Code Block
    actionscript
    actionscript
         protected function handleTimeout( passThroughData:Object ):void {
              Assert.fail( "Timeout reached before event");			
         }
    

:This tells the TestCase that this particular method should fail if it ever reaches the handleTimeout() method

Add the Test Case to the Suite

Before we can run this TestCase, we need to add it to a TestSuite.

  • Open the sampleSuite/SampleSuite.as file
  • Import the new asynchronous test case
    Code Block
    actionscript
    actionscript
         import sampleSuite.tests.TestAsync;
    
  • Add a new variable called testAsync of type TestAsync. Remember to declare it public this lets FlexUnit4 know that this is a test you need to run.
    Code Block
    actionscript
    actionscript
         public var testAsync : TestAsync
    
    Your SampleSuite.as now includes two distinct test cases.

Testing Asynchronously

  • Run your SampleSuite and you should see two distinct tests that pass, however it may take a moment longer to run than before. The disadvantage of asynchronous testing is that it does take more time, but many tests need to be handled asynchronously to be valid.
  • Next create a new TestMethod in your TestAsync.as TestCase. Name it testAsyncFail() and add the following code:
    Code Block
    actionscript
    actionscript
         [Test(async, description="An async test that will fail")]
         public function testAsyncFail() : void {
              timer.delay = 1000;
              var asyncHandler:Function = Async.asyncHandler( this, handleTimerComplete, 500, null, handleTimeout );
              timer.addEventListener(TimerEvent.TIMER_COMPLETE, asyncHandler, false, 0, true );
              timer.start();	
         }
    
  • Run your SampleSuite again and you should see a failure for this test as the timeout expired before the timer event fires. If you click on the test in the runner you will see the failure "Timeout reached before event". If you are using FlashBuilder 4 you can look in your FlexUnit Results tab and select the test to get this information about what caused the failure, since you don't have the test runner interface.

Combined Syntax

Above we created a test method by defining the asyncHandler() and addEventListener() on two separate lines. This can have some advantages if the developer is trying to manually remove and manage listeners. However, in the average case, this can be combined into a single statement like in the method below, timerShortWay():

Code Block
actionscript
actionscript
     [Test(async)]
     public function timerShortWay():void {
           timer.delay = 100;
           timer.addEventListener( TimerEvent.TIMER_COMPLETE, 
                Async.asyncHandler( this, handleTimerComplete, 500, null, handleTimeout ), false, 0, true );
           timer.start();			
     }

Add this method to your TestAsync.as case and re-run SampleSuite. You should now see three passing tests. Note that you re-used both the handleTimerComplete and handleTimeout from the previous example. You should see significant reuse in well-crafted handlers as you continue to develop test cases. This is where the passThroughData can come in handy.

Pass Through Data

Learning to use the passThroughData parameter of the asyncHandler() method will give you the most flexibility in writing reusable handlers. Any data passed into the handler will be passed to either the eventHandler on success or the timeoutHandler on a timeout.

Building on the previous example, here is another Timer test that uses the Timer's current count property. We'll call this test timerCount(). The first thing this method will do is create a generic object and add a property called repeatCount to that object.

We will set the repeatCount property of the timer to the value contained in this object. Previously, we always passed a null to the passThroughData property of the asyncHandler() method. This time, however, we will pass the object 'o' that you created at the beginning of the method. We instruct asyncHandler() to call handleTimerCheckCount when the TIMER_COMPLETE event occurs.

Code Block
actionscript
actionscript
     [Test (async)]
     public function timerCount() : void {
          var o:Object = new Object();
          o.repeatCount = 3;

          timer.repeatCount = o.repeatCount;
          timer.addEventListener(TimerEvent.TIMER_COMPLETE, 
               Async.asyncHandler(this, handleTimerCheckCount, 500, o, handleTimeout ), false, 0, true );
          timer.start();	
     }

Next, create a handleTimerCheckCount() method. It will call assertEquals() and compare the currentCount property of the Timer to the repeatCount property of our passthrough data.

Code Block
actionscript
actionscript
     protected function handleTimerCheckCount( event:TimerEvent, passThroughData:Object ):void {
          Assert.assertEquals( ( event.target as Timer ).currentCount, passThroughData.repeatCount );
     }

In our test, we tell the timer to count to repeat three times before it broadcasts the TIMER_COMPLETE message. Then, in our handleTimerCheckCount() method, we ensure that the Timer's currentCount (the number of times the Timer has repeated) is correct. The important part is that we have now created a generic handler function which uses data created during the test. This same handler could be used for many different tests. Many more examples of this concept will be reviewed when we start testing UIComponents in the next sections..

Previous | Next