Tom Sugden: FlexUnit Archives

January 11, 2008

EventfulTestCase: a FlexUnit extension for testing event dispatching

NOTE: The extension described in this post has now been incorporated into FlexUnit 3. The syntax has changed slightly, but the concepts described in this post remain. This feature is not yet available in FlexUnit 4, which is a major update, but work is underway to provide even better support for unit testing the event dispatching behavior of your classes.

Introduction

The events that a class dispatches form an important part of its contract with clients, yet for one reason or another they are seldom tested. Perhaps this is because event declarations are optional in Flex and specified as metadata, or perhaps it is because the FlexUnit assertions were mostly ported from JUnit and not tailored specifically to Flex. In either case, programming mistakes and design flaws in event logic can have far-reaching consequences, effecting any dependent event listeners and the classes beyond them.

To illustrate the importance of event testing, consider the case of a NuclearReactor class that dispatches temperatureChange events to indicate the effect of adding fuel. Perhaps we also have a MeltdownAlert class that listens for these events in order to raise an alert whenever a boundary temperature is crossed. The melodrama should make it obvious: if the NuclearReactor has a bug that prevents it dispatching events in the expected manner, the MeltdownAlert will never trigger and a catastrophe will unfold.

So it is clearly important to ensure event dispatching logic is correct. To promote the best-practice of unit testing the dispatch of events, I have written a small extension to the FlexUnit TestCase class that makes it easy: EventfulTestCase. This is a new base class for any tests that involve the dispatch of events. It supports both standard Flash events and also Cairngorm events.

The following ActionScript source files accompany this post:

Writing Eventful Test Cases

The process of writing an eventful test case involves first recording the expected events, then performing the action under test, and finally asserting that the expected events occurred. EventfulTestCase defines three kinds of functions to help with this:

  1. expect functions - used to record the events that are expected to occur during the test case.
  2. assert functions - used to assert that the expected events actually occurred.
  3. actual event getters - used to access the actual events that were heard so that more specific assertions can be carried out.

Example 1: testing the dispatch of normal events

Shown below is a simple example which asserts that the temperatureChanges event is dispatched when the addPlutonium() function is called.

public function testAddPlutonium() : void
{
   // record the expected event
   expectEvent( reactor, TemperatureChangeEvent.TEMPERATURE_CHANGE );
         
   // perform the action that is being tested
   reactor.addPlutonium( plutonium );
         
   // assert that the expected events occurred
   assertExpectedEventsOccurred( 
      "The temperature change event was not dispatched." );
}

If the expected event is not dispatched, the test case will fail and the developer will be presented with a failure message to assist in debugging. In this case, the message might look like: "The temperature change event was not dispatched. Expected events <temperatureChangedEvent> but heard events <reactorStoppedEvent>". Now the developer knows that the addPlutonium() function contains a logical error which causes the wrong event to be dispatched.

Example 2: testing the dispatch of Cairngorm events

The next example is a little more complex and asserts that two Cairngorm events have occurred. Since Cairngorm events are dispatched through the CairngormEventDispatcher singleton instead of a regular Flex EventDispatcher, the CairngormEventSource adapter class must be used as the event source.

public function testRefreshPlutoniumStocks() : void
{
   // record the expected events
   expectEvents( 
      CairngormEventSource.instance, // adapter for Cairngorm events
      GetLocalPlutoniumStocksEvent.EVENT_NAME,
      GetNationalPlutoniumStocksEvent.EVENT_NAME );
    
   // perform the action that is being tested
   reactor.refreshPlutoniumStocks();
 
   // assert that the expected events occurred
   assertExpectedEventsOccurred(
      "The Cairngorm events to fetch local and national " +
      "plutonium stocks were not dispatched." );
}

Example 3: performing more detailed assertions

The final example builds on the first, demonstrating how to use the actual event getters for performing more detailed assertions.

public function testAddPlutonium() : void
{
   // record the expected event
   expectEvent( reactor, TemperatureChangeEvent.TEMPERATURE_CHANGE );
         
   // perform the action that is being tested
   reactor.addPlutonium( plutonium );
         
   // assert that the expected events occurred
   assertExpectedEventsOccurred( 
      "The temperature change event was not dispatched." );

   // assert on the details of the actual event
   var temperatureChangeEvent : TemperatureChangeEvent = 
      TemperatureChangeEvent( lastActualEvent );
      
   var expectedTemperature : Number = 550;
      
   assertEquals(
      "The expected temperature change did not occur.",
      expectedTemperature,
      temperatureChangeEvent.temperature );
}

Known Limitations

EventfulTestCase only listens for the expected events and cannot hear unexpected events. The consequence of this is limited detail in the test failure messages. Only the expected events that actually occurred can be reported. The reason for this limitation is that the Flex framework does not enable us to listen to unspecified event types. In other words, we need to register listeners explicitly for each type of event that we want to listen to. One work-around would be to override the dispatchEvent() function of the EventListener framework class, but that would be overly intrusive. This limitation is not severe since the failure messages that can be provided are still reasonably useful.

It should also be noted that EventfulTestCase relies on the fact that most event dispatching takes place synchronously in Flex. When a function dispatches an event, we can be sure that any registered listeners will hear that event synchronously before the function continues and returns. In Flex applications, asynchronicity is typically confined to the I/O operations performed by the Flash Player, and these sit outside the normal realm of unit testing. It is usually possible to mock genuinely asynchronous behaviour, so that the class in question can be tested synchronously. When this is not the case, or for integration testing purposes, a more sophisticated solution is required.

Conclusion

The bugs and design flaws associated with event dispatching often slip the net when unit testing, allowing them to lurk in the deep and strike later. The EventfulTestCase extension helps to prevent this by making it quick and easy to test event dispatching. The process involves three stages: recording expectations, performing actions and asserting that the events occurred. As we attempt to build bigger and better Flex applications, we must test more thoroughly to ensure our success, and this must include testing events.

Posted by tsugden at 10:28 PM | Comments (7)