Unit 13 - Working with Asynchronous Operations

Download Download Unit Project Files

FlexUnit 4.x takes advantage of a variety of inherently asynchronous operations. Test Fixture, Test Theories, Loading External Data, and Parameterized testing take advantage of asynchronous setup. Some tests require a span of time in order to verify expected results. FlexUnit 4.x provides tools to validate asynchronous functionality.

Objectives:

After completing this lesson, you should be able to:

Topics

In this unit, you will learn about the following topics:

Understanding the need for async

Up until now, all tests in this text have been synchronous in nature. Tests run and assertions are made line by line. However, ActionScript 3.0 and Flex are asynchronous by nature. You may have to wait for an event, a database retrieval, or a timer to complete. Currently, any of these operations would be marked as a pass or failure immediately without waiting for the actual event to be received.

Consider the external data loading you have used so far in theories and parameter tests. Loading from an XML file is asynchronous, depending on the amount of data the load time of that file can change.

Without asynchronous support, a large portion of systems would remain untested creating an easy location for bugs to hide. Race conditions can potentially be the origin of huge bugs that, without asynchronous testing, can cause countless hours of debugging.

Introduction to the available async methods

FlexUnit 4 contains extensive support for asynchronous tests. All asynchronous tests are decorated with the annotation async which notifies the runner this test is not complete once the method has reached the closing brace.

[Test( async )]

Decorating a test with async causes the runner to hold that test in memory. The test runner relies on an async handler to notify when the test is complete. Omitting an async handler will cause the test to hang indefinitely. To prevent the test from hanging, we may also decorate the test with the timeout annotation.

[Test( async, timeout="1000" )]

The Async class in FlexUnit 4 contains numerous static methods for handling async testing.

Walkthrough 1: Creating a Simple Async Test with Timers

In this walkthrough you will perform the following tasks:

Steps

  1. Import the FlexUnit4Training_wt1.fxp project from the Unit13/Start folder. Please refer to Unit 2: Walkthrough 1 for instructions on importing a Flash Builder project.

  2. Create a new package named async.testcases in the tests directory.


    Create a basic async test

  3. In the async.testcases package create a new ActionScript class named BasicTimerTest.

  4. Remove the automatically created constructor from the class.

    package async.testcases {
        public class BasicTimerTest {
        }
    }


    Create an async test case

  5. Declare a new private property named timer of type Timer within the class.

    private var timer:Timer;

    If you did not use code-completion, add the import for flash.utils.Timer at this time.

  6. Declare setup and teardown methods for the class, each marked with [Before] and [After] metadata. The setup method will instantiate timer with a delay of 100 and a repeat count of 1. The teardown method will check if timer is running, stop it if necessary, and destroy the instance.

    [Before]
    public function setUp():void {
        timer = new Timer( 100, 1 );
    }
    
    [After]
    public function tearDown():void {
        if( timer && timer.running ) {
            timer.stop();
        }
    
        timer = null;
    }
  7. Write a test method named shouldCompleteTimer(). It will add an event listener for the TimerEvent.TIMER_COMPLETE event which will call an AsyncHandler() method. This asynchronous test must be marked with [Test( async )] metadata in order to be run asynchronously.

    [Test( async )]
    public function shouldCompleteTimer():void {
        timer.addEventListener( TimerEvent.TIMER_COMPLETE,
            Async.asyncHandler( this, handleWin, 100, timer, handleTimeout ),
            false, 0, true );
    }

    asyncHandler() calls a success handler or timeout handler depending on whether the TimerEvent.TIMER_COMPLETE event is dispatched before the timeOut limit.

    If you did not use code-completion, add the import statements for org.flexunit.async.Async and flash.events.TimerEvent at this time.

    Take a look at the Async.asyncHandler() method used within the addEventListener() method in the testTimerComplete() test.

    Async.asyncHandler( this, handleWin, 100, timer, handleTimeout )

    The prototype of this method is:

    Async.asyncHandler(testCase:Object, eventHandler:Function, timeout:int, passThroughData:Object=null,
     timeoutHandler:Function=null):Function

    This method references two functions: handleWin() and handleTimeout(). The method handleWin() is called if the TimerEvent.TIMER_COMPLETE occurs before the timeout, which is set at 100. handleTimeout() is the function called if the event is not dispatched before the timeout. handleWin() will receive both the event and passThroughData objects as arguments. The method handleTimeout() will only receives the passThroughData.

  8. Add a call to the timer.start() method on the last line of the shouldCompleteTimer() test method.

    [Test( async )]
    public function shouldCompleteTimer():void {
        timer.addEventListener( TimerEvent.TIMER_COMPLETE,
            Async.asyncHandler( this, handleWin, 100, timer, handleTimeout ),
            false, 0, true );
        timer.start();
    }

    You will need to declare both the handleWin() and handleTimeout() functions. They can be specified as protected because they are only run within the specific test case and not by the runner itself.

  9. Declare the handleWin() function as protected. It should accept two parameters, one named event of type Event, and the other named passThroughData of type Object.

  10. The handleTimeout() method should also be declared as protected and accept a single parameter named passThroughData of type Object.

    protected function handleWin( event:Event, passThroughData:Object ):void {
    }
    
    protected function handleTimeout( passThroughData:Object ):void {
    }
  11. Add a call to the Assert.assertEquals() method in the handleWin() method. It should take ( event.target as Timer ).currentCount and passThroughData.repeatCount as its arguments.

    protected function handleWin( event:Event, passThroughData:Object ):void {
        Assert.assertEquals( ( event.target as Timer ).currentCount, passThroughData.repeatCount );
    }
  12. Add a call to the Assert.fail() method in the handleTimeout() method. It should take the string "Pending event timed out" as its argument.

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

    If you did not use code-completion, add the import statements for flash.events.Event and org.flexunit.Assert at this time.

  13. Save BasicTimerTest.as.


    Create an async suite

  14. In the async.testcases package create a new ActionScript class named AsyncSuite. The package directory should appear as follows:

    PackageDirectory

    Figure 1: Package directory structure

    The AsyncSuite class will behave similar to the CircleSuite, running with the suite runner and calling tests within the flexUnitTests.cases.async package.

  15. Remove the automatically created constructor from AsyncSuite.as.

    package async.testcases {
        public class AsyncSuite {   
        }
    }
  16. Mark the AsyncSuite class definition with [Suite] and [RunWith("org.flexunit.runners.Suite")] metadata.

    package async.testcases {
        [Suite]
        [RunWith("org.flexunit.runners.Suite")]
        public class AsyncSuite {
        }
    }
  17. Add a public variable named test1 of type BasicTimerTest.

    public var test1:BasicTimerTest;
  18. Save AsyncSuite.as.

    Normally, a suite is not created to contain a single test case. In this case, we know ahead of time that the Async class will eventually contain more test files.


    Create a new top-level suite

    At this point, your testing environment includes test cases for the Circle class as well as a new one in the async.testcases package. Take this opportunity to create a new top-level suite that will run all the suites from the various packages.

  19. In the tests directory, create a new package named testcases.

  20. In the new testcases package, create an ActionScript class named AllSuites. This class has no superclass or interfaces.

  21. Remove the automatically created constructor from the new class and mark it with [Suite] and [RunWith("org.flexunit.runners.Suite")] metadata.

    package testcases {
        [Suite]
        [RunWith("org.flexunit.runners.Suite")]
        public class AllSuites {
        }
    }
  22. Add a public variable named circleSuite of type CircleSuite to the new class.

  23. Add another public varibale named asyncSuite of type AsyncSuite to the class.

    [Suite]
    [RunWith("org.flexunit.runners.Suite")]
    public class AllSuites {
        public var circleSuite:CircleSuite;
        public var asyncSuite:AsyncSuite;   
    }

    If you did not use code-completion, add the imports for math.testcases.CircleSuite and async.testcases.AsynSuite at this time.

  24. Save AllSuites.as.


    Run the new suite from the application

  25. Open the FlexUnit4Training.mxml file.

  26. Replace CircleSuite with AllSuites in the testsToRun.push() statement.

    testsToRun.push( CircleSuite );

    Becomes:

    testsToRun.push( AllSuites );

    If you did not use code-completion, add the import for the testcases.AllSuites at this time.

  27. Remove the import statement for math.testcases.CircleSuite from the FlexUnit4Training.mxml file.

  28. Save FlexUnit4Training.mxml.

  29. Run the FlexUnit4Training.mxml file.

    If FlexUnit4Training.mxml ran successfully you should see the following output in your browser window:

    TestsPassed

    Figure 2: FlexUnit tests passed

Understanding error handling with async

In Unit 5 you learned how to handle errors using the [Test(expects="ErrorType")] metadata. Asynchronous methods can handle errors in the same way. Errors thrown in the handler are still dealt with in the test method.

[Test( async, expects="Error" )]
public function testTimerComplete():void {
    timer.addEventListener( TimerEvent.TIMER_COMPLETE,
        Async.asyncHandler( this, handleWin, 100, timer, handleTimeout ),
        false, 0, true );
    timer.start();
}

protected function handleWin( event:Event, passThroughData:Object ):void { throw new Error(); }

As you can see, the Async.asyncHandler is called upon the TIMER_COMPLETE event, just as in the previous walkthrough. The resulting handler is simplistic, but clearly illustrates how the error is then handled through the test, where the metadata reads [Test( async, expects="Error" )]. There are several situations in which you need the message or the resulting handler to utilize passThroughData or additional custom information within the error thrown. In these cases, the expects annotation is insufficient. Here, the handler can be outfitted with a try-catch statement that handles a specific error and data potentially carried with that error.

[Test( async )]
public function testTimerComplete():void {
    timer.addEventListener( TimerEvent.TIMER_COMPLETE,
        Async.asyncHandler( this, handleWin, 100, timer, handleTimeout ),
        false, 0, true );
    timer.start();
}

protected function handleWin( event:Event, passThroughData:Object ):void { try { throw new TypeError(); } catch ( e:TypeError ) { assertThat( passThroughData, equalTo( timer ) ); return; } Assert.fail( "Incorrect error thrown" ); }

The try-catch statement is looking for a typeError, and then it continues to evaluate with an assertThat statement, expecting the equality of the timer and passThroughData. In these cases, the expects annotation will weaken the test. The test will only expect the kind of error specified, and it will not assert equality, such as is shown through the catch above.

Event based synchronous versus asynchronous

FlexUnit testing is based on the idea of synchronous tests occurring in order. These tests can throw events, errors, and deliver results, but they don't wait around for anything to happen, they just run.

Asynchronous tests are set apart through their metadata, and the asynchronous handlers are the only thing that can bring the test to completion. There are three primary kinds:

Normal asynchronous completion

When the test dispatches the expected event within the time limit, the eventHandler method is then called. Even though the desired event has been dispatched, the test does not automatically pass.

Async.asyncHandler(testCase:Object, eventHandler:Function, timeout:int, passThroughData:Object=null,
 timeoutHandler:Function=null)

Asynchronous timeout

Most Async methods are created with timeout variables. If the desired event is not dispatched within the time limit, the timeoutHandler is called.

When a timeoutHandler is specified, a timeout does not mark a test as a failure. If a timeout indicates failure you need to assert as such in the timeout method.

If a timeoutHandler is not passed in, the test merely completes with a generic timeout failure.

Async.asyncHandler(testCase:Object, eventHandler:Function, timeout:int, passThroughData:Object=null,
 timeoutHandler:Function=null)

Failure events

When using the Async.failOnEvent or Async.registerFailureOnEvent methods, specific events can be registered as an instantaneous failure.

Walkthrough 2: Handling an Event

In this walkthrough you will perform the following tasks:

Steps

  1. Create a new ActionScript class named PrimeNumberGeneratorTest.as in the async.testcases package within the tests directory.

    Alternatively, if you didn't complete the previous lesson or your code is not functioning properly, you can import the FlexUnit4Training_wt2.fxp project from the Unit13/Start folder. Please refer to Unit 2: Walkthrough 1 for instructions on importing a Flash Builder project.


    Create the fixture

  2. Mark the PrimeNumberGeneratorTest class with [RunWith("org.flexunit.runners.Parameterized")] metadata.

    [RunWith("org.flexunit.runners.Parameterized")]
    public class PrimeNumberGeneratorTest {
        public function PrimeNumberGeneratorTest() {
        }
    }

    You will be using JUnit style paramterized loading for this test. Because this style requires parameters to be set in the constructor, leave the constructor in place.

  3. Add a private variable named primeGenerator of type PrimeNumberGenerator to the class.

    private var primeNumberGenerator:PrimeNumberGenerator;

    If you did not use code-completion, add the import for net.digitalprimates.math.PrimeNumberGenerator at this time.

  4. Add a private static constant named TIMEOUT of data type int and set it to 500.

    private static const TIMEOUT:int = 500;
  5. Add another public static variable named numberLoader of type NumberDataHelper. Instantiate the variable with a URL string: "xml/numberList.xml"

    public static var numberLoader:NumberDataHelper = new NumberDataHelper( "xml/numberList.xml" );

    If you did not use code-completion, add the import for helper.NumberDataHelper at this time.

  6. Create a new static variable named data. Decorate it with the Parameters metadata and pass it a loader of numberLoader

    [Parameters(loader="numberLoader")]
    public static var data:Array;
  7. Add two private instance variables of type Number named value and length. In typical JUnit style, make the constructor take values for each as arguments, and set the instance variables when the constructor is run.

    private var value:Number;
    private var length:Number;
    
    public function PrimeNumberGeneratorTest( value:Number, length:Number ) {
        this.value = value;
        this.length = length;
    }

    All instance variables for the class should now be declared.

  8. Create a new method named setup() and a method named teardown(). Decorate them with the [Before( async )] and [After( async )] metadata, respectively.

    [Before( async )]
    public function setup():void {
    }
    
    [After( async )]
    public function teardown():void {
    }

    The async annotation notifies the test runner the setup() and teardown() methods are not complete when the end brace is reached. The PrimeNumberGenerator simulates this event through the use of a timer. You will need to specify a complete condition just as you would for an asynchronous test or the startup will hang indefinitely.

  9. Within the setup() method, create a new instance of primeNumberGenerator. On the next line, add a call to the method Async.proceedOnEvent() passing the arguments this, primeNumberGenerator and TIMEOUT.

    [Before( async )]
    public function setup():void {
        primeNumberGenerator = new PrimeNumberGenerator();
        Async.proceedOnEvent( this, primeNumberGenerator, PrimeGeneratorEvent.GENERATOR_READY,
         TIMEOUT );
    }

    Async.proceedOnEvent() causes the test runner to wait for the parameter IEventDispatcher to dispatch an event before proceeding. In this case the event is named: PrimeGeneratorEvent.GENERATOR_READY.

    If you did not use code completion, add the imports for org.flexunit.async.Async and net.digitalprimates.event.PrimeGeneratorEvent at this time.

  10. In the teardown() method, remove the instance of primeNumberGenerator.

    [After( async )]
    public function tearDown():void {
        primeNumberGenerator = null;
    }
  11. Your completed asynchrounous startup should now appear as:

    [RunWith("org.flexunit.runners.Parameterized")]
    public class PrimeNumberGeneratorTest {
        private var primeNumberGenerator:PrimeNumberGenerator;
    
        public static const TIMEOUT : int = 500;
    
        public static var numberLoader:NumberDataHelper =
         new NumberDataHelper( "xml/numberList.xml" );
    
        [Before( async )]
        public function setup():void {
            primeNumberGenerator = new PrimeNumberGenerator();
            Async.proceedOnEvent( this, primeNumberGenerator, PrimeGeneratorEvent.GENERATOR_READY,
             TIMEOUT );
        }
    
        [After( async )]
        public function teardown():void {
            primeNumberGenerator = null;
        }
    
        private var value:Number;
        private var length:Number;
    
        public function PrimeNumberGeneratorTest( value:Number, length:Number ) {
            this.value = value;
            this.length = length;
        }
    }


    Create the generator test

  12. Add a new test method named shouldCreatePrimeArray() to the class, mark it with [Test(async)] metadata. This method creates a new async handler to the handleEvent() method, passing in the arguments this, primeNumberGenerator, PrimeNumberGenerator.GENERATION_COMPLETE, handleComplete and TIMEOUT.

    [Test(async)]
    public function shouldCreatePrimeArray():void {
        Async.handleEvent( this, primeNumberGenerator, PrimeGeneratorEvent.GENERATION_COMPLETE,
         handleComplete, TIMEOUT );
    }
  13. Add a call to primeNumberGenerator.generatePrimes() method, passing in the instance variable value as its argument.

    [Test(async)]
    public function shouldCreatePrimeArray():void {
        Async.handleEvent( this, primeNumberGenerator, PrimeGeneratorEvent.GENERATION_COMPLETE,
         handleComplete, TIMEOUT );
    
        primeNumberGenerator.generatePrimes( value );
    }
  14. Create the handleComplete() method referenced in the Async.handleEvent() method from the previous step. It needs to accept two parameters, one named event of type PrimeGeneratorEvent, and another named passThroughData of type Object.

    protected function handleComplete( event:PrimeGeneratorEvent, passThroughData:Object ):void {
    }

    The Async.handleEvent() method above declares no timeoutHandler(), so it will just use the default message. This will suffice for this walkthrough, however it can be added as the last argument of the Async.handleEvent() method if necessary.

  15. Add a call to the assertThat() method within the handleComplete method. It should assert that event.primeList.length is equal to length.

    protected function handleComplete( event:PrimeGeneratorEvent, passThroughData:Object ):void {
        assertThat( event.primeList.length, equalTo( length ) );
    }

    If you did not use code-completion, add the imports for org.flexunit.assertThat and org.hamcrest.object.equalTo at this time.

  16. Save PrimeNumberGeneratorTest.as.


    Add NumberGeneratorTest to the AsyncSuite

  17. Open AsyncSuite.as in the async.testcases package. Add a public variable named test2 of type PrimeNumberGeneratorTest.

    package async.testcases {
        [Suite]
        [RunWith("org.flexunit.runners.Suite")]
        public class AsyncSuite {
            public var test1:BasicTimerTest;
            public var test2:PrimeNumberGeneratorTest;
        }
    }
  18. Run the FlexUnit4Training.mxml file.

    If FlexUnit4Training.mxml ran successfully, you should see the following in your browser window:

    TestsPassed

    Figure 1: FlexUnit tests passed

Chaining async operations

Frequently, integration tests require a sequence of events before the actual test can be performed. In simple event sequences, chaining async handlers is an effective way to test component behaviors.

Consider the case of an alarm clock that cycles a series of pictures on expiration:

  1. A user sets an alarm.
  2. When the alarm expires, the system displays a series of pictures on an interval.

Now consider all the steps that occur when testing this scenario:

  1. The test would need to set an alarm.
  2. The system would need to wait for alarm expiration.
  3. The system would display a picture.
  4. The test would have to wait for the picture display time to expire.
  5. Repeat steps 3 and 4.
  6. The test would have to wait for the system to complete displaying all the pictures.

This is a testable use case. The testing concern is whether the component fires the events and system displays all the pictures. You could use a chain of async handlers.

[Test( async, ui )]
public void shouldSubmitForm():void {
    //Create a new alarm clock
    //Set an alarm time
    //Proceed on event: "alarmExpired"
    //Wait for the picture
    //Proceed on event: "pictureLoaded"
    //Display the picture
    //Proceed on event: "pictureDisplayExpired"
    //Load and display other pictures
    //Proceed on event: "allPicturesDisplayed"
    //Verify expected number of pictures displayed
}

As you can see, this can rapidly become very complex as we wait for event after event. Each of these asynchronous operations could require its own async handler leading to a very large test class. This is a simple example, but many UI components, which we will cover in the next unit, can rapidly devolve into a massive integration test. In addition, some components, especially higher level components, require user interaction that cannot be easily simulated through the use of async chains.

FlexUnit 4 contains a special tool for handling these types of use cases called sequences.

Understanding sequences

Sequences are methods by which a developer can create a series of tests, execute the steps and handle the resulting sequence completion. Sequences control the flow of a components execution.

Sequences have the ability to:

  1. Call methods
  2. Set properties
  3. Wait for events
  4. Dispatch events
  5. Wait for binding

Sequences require the test case to use the custom runner named SequenceRunner. Each sequence is created by instantiating a sequence with the current test case, adding a series of tests, adding an optional assert handler and finally running the sequence.

A simple sequence for a timer would appear as follows:

[Test( async )]
public function shouldCompleteTimerSequence():void {
    var timer:Timer = new Timer( TIMEOUT );
    var sequence:SequenceRunner = new SequenceRunner( this );

sequence.addStep( new SequenceCaller( timer, timer.start ) );
sequence.addStep( new SequenceWaiter( timer, TimerEvent.TIMER, TIMEOUT2 ) );
sequence.addStep( new SequenceWaiter( timer, TimerEvent.TIMER, TIMEOUT2 ) );
sequence.addStep( new SequenceWaiter( timer, TimerEvent.TIMER, TIMEOUT2 ) );
sequence.addStep( new SequenceCaller( timer, timer.stop );

sequence.addAssertHandler( handleSequenceComplete, null );

sequence.run();

}

In this example, the sequence will: start a timer, wait for the timer to cycle three times, stop the timer and make an assertion. The advantage to this setup is if the timer needed to cycle two more times you would just need to add two more sequence waiters.

In the next walkthrough, you will create your own sequence.

Walkthrough 3: Creating a chain of events with Sequences

In this walkthrough you will perform the following tasks:

Steps

  1. Create a new ActionScript class named ServiceSequenceTest.as in the async.testcases package within the tests directory.

    Alternatively, if you didn't complete the previous lesson or your code is not functioning properly, you can import the FlexUnit4Training_wt3.fxp project from the Unit13/Start folder. Please refer to Unit 2: Walkthrough 1 for instructions on importing a Flash Builder project.


    Create the fixture

  2. Remove the automatically generated constructor. You will not need it for this test.

  3. Add a new private static const named TIMEOUT of type Number and set it to the value 500.

    private static const TIMEOUT:Number = 500;
  4. Add a private variable named service of type ServiceStub to the class.

    private var service:ServiceStub;

    If you did not use code-completion, add the import for net.digitalprimates.stub.ServiceStub at this time.

  5. Create the methods setup() and teardown() and decorate them with the [Before] and [After] metadata respectively. Inside the setup() method create a new instance of service variable. In the teardown() method, set it to null so that it may be garbage collected.

    [Before]
    public function setup():void {
        service = new ServiceStub();
    }
    
    [After]
    public function teardown():void {
        service = null;
    }


    Prepare the test case

  6. Create a new test called shouldCompleteRequest(). Mark it with [Test(async)] metadata.

    [Test( async )]
    public function shouldCompleteRequest():void {
    }

    A request is considered complete when a call to the service instance's shouldCompleteRequest() method has created a connection, sent the request and shutdown the connection. To create a test that listens for all of these events you may either create an asynchrounous chain of handlers or use a sequence. For this test, you will be using a sequence.

  7. Inside the body of shouldCompleteRequest(), declare a new variable sequence of the type org.fluint.sequence.SequenceRunner. Make sure you are using the correct SequenceRunner class.

    [Test( async )]
    public function shouldCompleteRequest():void {
        var sequence:SequenceRunner = new SequenceRunner( this );
    }

    If you did not use code-completion, add the import for org.fluint.sequence.SequenceRunner at this time.


    Create the Sequence

    The next block will setup all the SequenceCallers and SequenceWaiters for the SequenceRunner. In this step you will want to test that when sendRefreshRequest() is called service creates a connection to the server, sends a refresh request and closes the connection. It is phrased politely because the server may take longer to complete this request depending on various factors such as connection speed, number of requests, etc.

    To add these steps, you will use the sequence's addStep() method. This method has a single parameter, which must be of type org.fluint.sequence.ISequenceStep. A sequence does not execute until its run() method is called.

  8. Add a call to sequence.addStep() passing as an argument new SequenceCaller(). Pass the SequenceCaller constructor the arguments service and service.sendRefreshRequest.

    sequence.addStep( new SequenceCaller( service, service.sendRefreshRequest ) );

    If you did not use code-completion, add the import for org.fluint.sequence.SequenceCaller at this time.

    SequenceCaller sets up an asynchronous delay for the method call. Instead of calling it now, the method will be called when the sequence runs.

  9. Next, add a step to the sequence passing as an argument a new instance of the SequenceWaiter class. Pass it service, StubServiceEvent.CONNECTION_CREATED, and TIMEOUT as its arguments.

    sequence.addStep( new SequenceWaiter( service, StubServiceEvent.CONNECTION_CREATED, TIMEOUT ) );

    If you did not use code-completion, add the imports for org.fluint.sequence.SequenceWaiter and net.digitalprimates.event.StubServiceEvent at this time.

    A SequenceWaiter creates an asynchronous handler for the parameter event. When this event is dispatched, the sequence will continue. If the event is not dispatched before the timeout, the timeout handler is called. In this case you are using the default timeout handler which will throw an error. When dealing with a long sequence it is generally best practice to create a custom handler for each SequenceWaiter to avoid confusion.

  10. On the next line add a call to the addStep() method that instantiates a new SequenceWaiter with arguments service, StubServiceEvent.RECEIVED_REQUEST, and TIMEOUT.

    sequence.addStep( new SequenceWaiter( service, StubServiceEvent.RECEIVED_REQUEST, TIMEOUT ) );
  11. Add a final SequenceWaiter with arguments service, StubServiceEvent.SHUTDOWN_RECEIVED, and TIMEOUT.

    sequence.addStep( new SequenceWaiter( service, StubServiceEvent.SHUTDOWN_RECEIVED, TIMEOUT ) );

    The expected sequence is now set. However, there is no assert set for the completion of the sequence. Currently, as long as all the events are received in order the test is a success. If any of the events are not received within the specified timeout limit, the test fails.

  12. Add a call to service.addAssertHandler() passing handleCompleteRequest and null.

    sequence.addAssertHandler( handleCompleteRequest, null );

    The addAssertHandler adds an async handler for when the sequence completes successfully and will call the method specified. In this case, it will call handleCompleteRequest().

  13. To complete the sequence, call sequence.run() within the shouldCompleteRequest() method.

    sequence.run();

    Your complete shouldCompleteRequest() method should now appear as follows:

    [Test( async )]
    public function shouldCompleteRequest():void {
        var sequence:SequenceRunner = new SequenceRunner( this );
    
        sequence.addStep( new SequenceCaller( service, service.sendRefreshRequest ) );
        sequence.addStep( new SequenceWaiter( service, StubServiceEvent.CONNECTION_CREATED,
         TIMEOUT ) );
        sequence.addStep( new SequenceWaiter( service, StubServiceEvent.RECEIVED_REQUEST,
         TIMEOUT ) );
        sequence.addStep( new SequenceWaiter( service, StubServiceEvent.SHUTDOWN_RECEIVED,
         TIMEOUT ) );
    
        sequence.addAssertHandler( handleCompleteRequest, null );
    
        sequence.run();
    }

    If you did not use code-completion, add import statements for, org.fluint.sequence package.SequenceCaller and org.fluint.sequence package .SequenceWaiter at this time.

  14. Create the handleCompleteRequest() function. This function takes two paramters; one named event of type Event and another named passThroughData of type Object.

    protected function handleCompleteRequest( event:Event, passThroughData:Object ):void {
    }

    If you did not use code-completion, add the import for flash.events.Event at this time.

  15. Add a call to assertFalse() within the handleCompleteRequest() method, asserting that service connection has been terminated.

    protected function handleCompleteRequest( event:Event, passThroughData:Object ):void {
        assertFalse( service.connected );
    }

    If you did not use code-completion, add the imports for org.flexunit.assertThat at this time.

  16. Save ServiceSequenceTest.as.


    Add the new case to AsyncSuite

  17. Open the AsyncSuite.as file in the async.testcases package.

  18. Add a new public variable named test3 of type ServiceSequenceTest to the class.

    package async.testcases {
        [Suite]
        [RunWith("org.flexunit.runners.Suite")]
        public class AsyncSuite {
            public var test1:BasicTimerTest;
            public var test2:PrimeNumberGeneratorTest;
            public var test3:ServiceSequenceTest;
        }
    }
  19. Save the AsyncSuite.as file.

  20. Run the FlexUnit4Training.mxml file.

    If FlexUnit4Training.mxml ran successfully you should see the following output in your browser window:

    TestsPassed

    Figure 1: FlexUnit tests passed.

Summary

Navigation