« Drop City | Main | Making Applications With Degrafa and ObjectHandles »

New Event Testing Framework in FlexUnit

Part of the new FlexUnit release is a new TestCase subclass called EventfulTestCase. It provides convenience methods for testing objects that emit events. This new class can streamline your testing code and help catch unexpected events. Below is some sample code that demonstrates this new framework.

To use the new event testing framework subclass EventfulTestCase instead of TestCase.

package com.neophi.test {
    import flexunit.framework.EventfulTestCase;

    public class EventTest extends EventfulTestCase {
        // Test code here
    }
}

In a particular test method once you have the object that you want to test for assertions against, you call a new helper function called listenForEvent(). It takes three arguments:

  • source: the object that is to be listened on for the dispatched event
  • type: the type of event that the source object might dispatch
  • expected: whether the event is expected to be dispatched or now; defaults to EVENT_EXPECTED, the other choice is EVENT_UNEXPECTED (both defined in EventfulTestCase)

After your code has exercised the source to emit events, you make a call to assertEvents() with an optional message to make sure the events happened as planned. A quick example of a positive test for an event would look like this:

public function testEventExpectedPass():void {
    var eventEmittingObject:EventDispatcher = new EventDispatcher();

    listenForEvent(eventEmittingObject, Event.COMPLETE, EVENT_EXPECTED);
    eventEmittingObject.dispatchEvent(new Event(Event.COMPLETE));

    assertEvents("Expecting complete event");
}

After the event source is setup, we notify the testing framework what event we are expecting, emit the event, and then assert that is happened. In a more typical example the dispatchEvent() call would be part of the code under test. Also you aren't limited to listening for just a single event at a time. This example is just demonstrating the test framework API. When setting up an expected event via listenForEvent() the third argument defaults to EVENT_EXPECTED.

If an expected event doesn't get dispatched, the test harness will report an error as this next test demonstrates.

public function testEventExpectedFail():void {
    var eventEmittingObject:EventDispatcher = new EventDispatcher();

    listenForEvent(eventEmittingObject, Event.COMPLETE, EVENT_EXPECTED);

    assertEvents("Expecting complete event");
}

Testing for unexpected events works the same way but the fail and pass conditions are reversed.

public function testEventUnexpectedPass():void {
    var eventEmittingObject:EventDispatcher = new EventDispatcher();

    listenForEvent(eventEmittingObject, Event.COMPLETE, EVENT_UNEXPECTED);

    assertEvents("Not expecting complete event");
}

In this case the COMPLETE event is unexpected so by not firing the event the assertion passes, which is what we want. Just to flesh out the example, if an event is unexpected but does fire the assertEvents() call will fail as in this example:

public function testEventUnexpectedFail():void {
    var eventEmittingObject:EventDispatcher = new EventDispatcher();

    listenForEvent(eventEmittingObject, Event.COMPLETE, EVENT_UNEXPECTED);
    eventEmittingObject.dispatchEvent(new Event(Event.COMPLETE));

    assertEvents("Not expecting complete event");
}

In this case because an unexpected event fired it is an error.

Besides listenForEvent() and assertEvents(), two properties are exposed for examining the events that are listened for: lastDispatchedExpectedEvent holds the last event captured by listenForEvent() and dispatchedExpectedEvents holds all of the events captured. This last example shows the ability to listen for multiple events in different states, how events not registered with the listener are ignore, and uses these two convenience properties.

public function testMultipleEventsPass():void {
    var eventEmittingObject:EventDispatcher = new EventDispatcher();

    listenForEvent(eventEmittingObject, Event.INIT, EVENT_EXPECTED);
    listenForEvent(eventEmittingObject, Event.COMPLETE, EVENT_EXPECTED);
    listenForEvent(eventEmittingObject, ErrorEvent.ERROR, EVENT_UNEXPECTED);

    eventEmittingObject.dispatchEvent(new Event(Event.INIT));
    eventEmittingObject.dispatchEvent(new Event(Event.CHANGE));
    eventEmittingObject.dispatchEvent(new Event(Event.COMPLETE));

    assertEvents("Multiple events");

    assertEquals(Event.COMPLETE, lastDispatchedExpectedEvent.type);
    assertEquals(2, dispatchedExpectedEvents.length);
}

Some closing notes:


  • The order that the events are added by listenForEvent() doesn't matter. The asserts just check that events were or were not fired.

  • The event assertion framework will work with asynchronous events. You'll just want to wait to call assertEvents() in the final handler for your test.

  • The number of times that an event is dispatched doesn't matter.

  • assertEvents() can be called multiple times.

Tags: as3 events flexunit

Comments

This definitely feels like an improvement in terms of making less work for writing unit tests. One question, and one comment:

So then, to assertEvents that are asynchronous, you still have to create an addAsync handler to perform the assertion? (I assume this is what you mean by "final handler".)

It's surprising that there is a completely new class to extend for the new functionality, rather than adding it to TestCase. Presumably this is to avoid breaking any existing test cases where an "assertEvents" method has been added by users. But it does seem to leave open the subject of how to standardize on usage:

  • Always extend EventTestCase instead of TestCase, in case someone wants to add an event-based test method at some point in the future. This isn't so bad, although it would seem that extending EventTestCase is a way of signaling to a reader of the code that the unit test intends to do something EventTestCase-specific.
  • Extend TestCase for standard unit tests (i.e., "class FooTest extends TestCase") and then, when the programmer wants to take advantage of the new functionality, create a new "class FooEventTest extends EventTestCase" for those methods.
  • Extend TestCase for standard unit tests, and then modify it to extend EventTestCase if a test method is added which wants to leverage the new functionality. Classes continue to be named "FooTest", regardless of whether they extend TestCase or EventTestCase.
It's not that having to make this choice is bad, it's just one of those things that the team may wish to come to an agreement on when upgrading to the new FlexUnit. Arguably any team finding itself having to make this decision is already doing things right. ;)

Gorilla Logic has open sourced FlexMonkey, a record/playback tool for Flex that generates FlexUnit test cases. You can check it out at http://flexmonkey.googlecode.com.
Yes, please clarify what is meant by "the final handler for your test" in the context of testing asynchronous events.