« July 2008 | Main | September 2008 »

August 27, 2008

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

August 25, 2008

Drop City

T.C Boyle's Drop City follows the life of a hippie community forced to move, Alaskan frontiers people, and how their lives become intertwined. Not having grown up in the 1960's Boyle's portrayal of the hippie community felt more realistic then the idealized version commonly presented elsewhere. Likewise having grown up frequently camping and spending extensive time outdoors, the Alaskan narrative felt realistic, even having never tried to live completely off the land.

One of my biggest complaints with the book is the number of minor characters introduced whose stories are never really satisfied. Unlike the unnamed characters on Lost always milling around on the beach in the background, some of Boyle's characters are fleshed out enough to leave you hanging at the end of the book when never heard from again. Thankfully the fate of all of the major characters are dealt with otherwise I'd be tempted to say the book was complete rubbish.

Tags: books

August 22, 2008

Allurent is moving and mannequin suggestions

On Monday Allurent will be in a new larger office. We are moving to 222 Third Street,
Cambridge, MA 02142, which is in the American Twine Building. Our phone number is changing to 617-374-8800.

[Update: I've posted some pictures of our new digs.]

A different office decor is being designed, so there was a raffle for those interested to pick up any of the old Allurent office decorations. Since most of our initial clients were apparel focused, we had mannequins in the office to give it a retail theme. I won the female mannequin and after an adventurous ride bringing it home on my bicycle (pictures coming soon) I'm trying to figure out what to do with it. It's a reclined model mannequin which makes it a little harder so all suggestions are welcome. A picture of the mannequin is below.

Tags: allurent mannequin

August 21, 2008

New FlexUnit UI

The newest version of FlexUnit released on Adobe's Open Source site includes a spiffy new UI. It's much easier to see what test failed, where, and why. It also includes a feature to help identify tests that don't make any assertions! Below are a couple of screen shots showing the new UI in action.

Identify tests that don't make any assertions:

More easily see how a test failed and where:

Tags: flexunit

FlexUnit Moved to Adobe's Open Source Site

Adobe has decided to move FlexUnit to their Open Source site. You can now download and find FlexUnit at http://opensource.adobe.com/wiki/display/flexunit/Flexunit. As part of this move, they have also released a new version. The change history is a little sparse so if I find anything of note I'll write up a post.

Tags: flexunit

August 20, 2008

Odd addAsync() behavior when handling asynchronous events

There is some subtle behavior in FlexUnit's addAsync() that can lead to false positives and false negatives. In each case code that one expects to be called fails to run, which means that your tests may not be covering everything that you hoped they would. To kick off this discussion I'll present some code that should fail, but doesn't.

package com.neophi.test {
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flexunit.framework.TestCase;
    import mx.core.Application;

    public class AsyncTest extends TestCase {
        private var count:int;
        private var eventDispatcher:EventDispatcher;

        public function testAsyncLater():void {
            eventDispatcher = new EventDispatcher();
            eventDispatcher.addEventListener(Event.COMPLETE, addAsync(stage1, 100));
            Application.application.callLater(eventDispatcher.dispatchEvent, [new Event(Event.COMPLETE)]);
        }

        private function stage1(event:Event):void {
            count++;
            if (count == 1) {
                // This code will not cause the listener function to be called again
                Application.application.callLater(eventDispatcher.dispatchEvent, [new Event(Event.COMPLETE)]);
            } else {
                fail("Fail");
            }
        }
    }
}

This example creates an addAsync() wrapped function to listen for Event.COMPLETE. The application's callLater() is used to simulate the asynchronous firing of that event at some point in the future. The first time stage1() is called the count increments and another event is fired. When this second event is handled the test should fail but doesn't. The reason is that another addAsync() wasn't registered with FlexUnit. When the second event fires FlexUnit quietly ignores it, leading to a false positive. To correct this situation we need to register a new addAsync() that listens for the second event. Luckily FlexUnit's addAsync() always returns the same function reference so there is no need to remove the first listener, calling addEventListener() again just updates the existing listener. The rewritten stage1() function looks like this:

private function stage1(event:Event):void {
    count++;
    if (count == 1) {
        // This code works correctly
        eventDispatcher.addEventListener(Event.COMPLETE, addAsync(stage1, 200));
        Application.application.callLater(eventDispatcher.dispatchEvent, [new Event(Event.COMPLETE)]);
    } else {
        fail("Fail");
    }
}

This test now correctly fails when run. The false negative happens if we forgot to use callLater() to fire the second event. An unexpected failure message results if stage1() was changed as follows:

private function stage1(event:Event):void {
    count++;
    if (count == 1) {
        eventDispatcher.addEventListener(Event.COMPLETE, addAsync(stage1, 200));
        // This code will cause an asynchronous function timeout error
        eventDispatcher.dispatchEvent(new Event(Event.COMPLETE));
    } else {
        fail("Fail");
    }
}

The test fails with the message: "Error: Asynchronous function did not fire after 200 ms". Internally FlexUnit makes a check to see if it should be waiting for an event, if it isn't expecting one, the new event is discarded. In this case I find the failure message misleading. It wasn't that the asynchronous function wasn't called, just that it wasn't called asynchronously. This scenario can easily crop up if you are using mock objects and forgot to delay an operation that normally fires events asynchronously, like simulating an HTTP GET request. Using the application's callLater() is a quick way to introduce the needed asynchronous behavior.

Tags: addasync asynchronous flexunit

August 12, 2008

Patching iTerm

When I went searching for different terminal application for the Mac I found iTerm and have been very happy with it. It offers enough preferences to suit customization to my taste and it offers I couple of features I fell in love with from SecureCRT. In particular it offers a right-click open in browser option. I'm a luddite when it comes to working with email and prefer to use procmail, SpamAssassin, and Pine versus any of the various other email clients.

The problem with text only email is HTTP links that span multiple lines or get formatted with line breaks and spaces. Highlighting such a link and trying to launch it with the iTerm browser hook does nothing. I decided to "fix" this and as such created a small patch to iTerm Build 0.9.5.0611 that removes all white space from the highlighted text before handing it off to the browser. It seems to be working! Prior to this I've done no Objective-C programming so for all I know I've created a big memory leak. For anyone interested rough notes on what I did are below.

1. Getting the source
The latest CVS source doesn't build, so I pulled what seems to be the source for the last released version available for download

cvs -d:pserver:anonymous@iterm.cvs.sourceforge.net:/cvsroot/iterm login
cvs -z3 -d:pserver:anonymous@iterm.cvs.sourceforge.net:/cvsroot/iterm co -D "2007-07-01" -P iTerm

2. Patching PTYTextView.m
The _openURL method in PTYTextView.m handles launching the browser for the selected text. I modify the string prior to the protocol check. Since I'm still running on 10.4 I couldn't use some of the newer string manipulation methods.

3145c3145,3153
<
---
>
>     NSMutableString* tempString = [NSMutableString stringWithCapacity:[trimmedURLString length]];
>     [tempString setString:trimmedURLString];
>     [tempString replaceOccurrencesOfString:@" " withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [tempString length])];
>     [tempString replaceOccurrencesOfString:@"\t" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [tempString length])];
>     [tempString replaceOccurrencesOfString:@"\n" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [tempString length])];
>     [tempString replaceOccurrencesOfString:@"\r" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [tempString length])];
>     trimmedURLString = tempString;
>

3. Compiling and Installing
From a terminal I ran make Deployment and then copied the resulting build/Deployment/iTerm.app over the existing version I had in my Application directory. The one change was that the preferences file name changed so I also ran:

cp ~/Library/Preferences/iTerm.plist ~/Library/Preferences/net.sourceforge.iTerm.plist

Tags: iterm mac

August 7, 2008

Tomcat, Spring Web MVC, and Exceptions

I've been doing some server side development lately and using Spring 2.5.5 with Tomcat 6.0.18. One of the features built in is intelligent exception handling through implementations of HandlerExceptionResolver. In particular there is easy support to "fix" the HTTP response code for errors to be 200 so that Flash doesn't croak. I got my configuration setup but no matter what I did I kept getting back the default Tomcat 500 error page. A little code tracing showed that my configuration was valid and my Velocity based error page was getting picked up and processed but not reflected in the response. Turns out that Spring 2.5.5 and Tomcat 6.0.18 (and earlier versions) don't play well together. A bug has been logged against Spring so that if it generates an error response it clears out the Servlet error request attributes that Tomcat examines to determine if it should return its own built in error page. Alas as of today you'll have to grab a nightly snapshot of Spring to pickup the fix or manually patch DispatcherServlet.java.

Tags: exception java spring tomcat