How to Design for Unit Testing in Flex

Almost every user-facing product at Rosetta Stone is written in Flash using ActionScript 3 and the Flex framework. In order to help improve our confidence in the quality of our many sizable applications, we, like so many others, turn to unit testing!

In our experience, the difficulty of programmatically testing graphical applications of nontrivial size is due to two features of Flex:

  • Graphical applications are often characterized by asynchronous, user-initiated program execution flows. Reasoning about asynchronous behavior, like any concurrent behavior, is hard.
  • Graphical user interface frameworks like Flex encourage the storage of program state in user interface components. This state can be difficult to programmatically verify. Additionally, frequently changing layout can make tests brittle and hard to maintain.

In this series of blog posts, I hope to explain some of the approaches we’ve taken to dealing with these challenges. To kick things off, let’s talk about how to verify program state in a simple MXML application:

screeshot
<?xml version="1.0" encoding="utf-8"?>
<!-- UserView.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  layout="absolute">
<mx:Script>
  <![CDATA[
    import mx.controls.Alert;
    public function submit():void {
      //TODO implement fancy submission logic here.
      userFirstNameTextInput.text = "";
      userLastNameTextInput.text = "";
      submitButton.enabled = false;
      userFirstNameTextInput.editable = false;
      userLastNameTextInput.editable = false;
    }
  ]]>
</mx:Script>
  <mx:VBox>
    <mx:HBox>
      <mx:Label text="First Name:" />
      <mx:TextInput id="userFirstNameTextInput" />
    </mx:HBox>
    <mx:HBox>
      <mx:Label text="Last Name:" />
      <mx:TextInput id="userLastNameTextInput" />
    </mx:HBox>
    <mx:Button id="submitButton" label="submit" click="submit()" />
  </mx:VBox>
</mx:Application>

Now consider a unit test that’s trying to assert some truths about this program’s behavior:

public function testThatUserFirstAndLastNamesStartOutEmpty():void {
  var view:UserView = new UserView();
  assertEquals( view.userFirstNameTextInput.text, "");
  assertEquals( view.userLastNameTextInput.text, "");
}

public function testThatUserFirstAndLastNamesCanBeSet():void {
  var view:UserView = new UserView();
  view.userFirstNameTextInput.text = "Trygve";
  assertEquals( view.userFirstNameTextInput.text, "Trygve");
  view.userLastNameTextInput.text = "Reenskaug"
  assertEquals( view.userLastNameTextInput.text, "Reenskaug");
}

public function testThatSubmitDisablesForm():void {
  var view:UserView = new UserView();
  view.userFirstNameTextInput.text = "Trygve";
  view.userLastNameTextInput.text = "Reenskaug"
  assertTrue( view.submitButton.enabled );
  view.submitButton.dispatchEvent(
    new MouseEvent( MouseEvent.CLICK ) );
  assertFalse( view.submitButton.enabled );
  assertFalse( view.userFirstNameTextInput.editable );
  assertFalse( view.userLastNameTextInput.editable );
}

Whoops! If we try to run this test, we’ll get a runtime error telling us that the userFirstNameTextInput attribute of UserView has not yet been initialized. This is due to Flex’s phased instantiation process. We can’t depend on UserView‘s child components being instantiated until UserView has been parented and then initialized.

We could, in theory, try to fix the above test by parenting UserView to the static Application.application, making our test an asynchronous test, and adding an event listener for the “initialize” event which finally asserts our program state.

Alternatively, we could try to skip the asynchronous instantiation process by parenting UserView and then calling initialize() on the UserView directly to instantiate its child components within the calling thread of execution. However, this approach breaks down quickly when the user interface is more dynamic. Consider, for example, an animation that is played before the child components are ready to be used. In order to guarantee that our view is ready to be programmatically tested, we need to either

  1. fall back to using an asynchronous test and be sure that we’re listening for the right sequence of events, or
  2. try to find a way to complete the animations synchronously.

#1 is a brittle solution. It is highly coupled to the view’s animation implementation. Solution #2 drastically changes the behavior of our view, potentially invalidating our tests!

Wow, that’s a lot of complexity to test such a simple program. Let’s not do that.

Instead, let’s try and encapsulate our program state in a Model object! Many moderate-to-large sized Flex applications are written using the Model-View-Controller design idiom. To review, it breaks down a graphical application into three components:

mvc
  • The Model comprises application state and business logic
  • The View is a user-friendly presentation of that application state
  • The Controller translates user interactions (e.g. button clicks) into actions on the model

In our running example, we could factor a good portion of the state of our program into a simple ActionScript model type. In order to do this, we should ask ourselves the question: “What is the runtime state of my program?” In our simplistic example, the runtime state and behavior of our program can be captured as:

  • The Strings representing the user’s first and last names
  • Initial value is “”
  • Allowable transitions: any to any
  • The enabled state of the submit button and the editable states of the text fields
  • Initial value is true
  • Allowable transitions: true to false

Let’s make the attributes bindable so that the view can be automatically updated to reflect the state of the model. In fact, we can completely obviate the Controller’s job by using a combination of MXML’s

  • Bindings to seamlessly update view state from model state
  • Inline ActionScript to translate view events (e.g. button clicks) into actions on the model (e.g. invocation of the submit() function)
mvc
package {
  [Bindable]
  public class User {
    public function User() {
      firstName = "";
      lastName = "";
      submitEnabled = true;
      editable = true;
    }

    public function submit():void {
      //TODO implement fancy submission logic here.
      if( submitEnabled ) {
        submitEnabled = false;
        editable = false;
      }
    }

    public var firstName:String;

    public var lastName:String;

    public var submitEnabled:Boolean;

    public var editable:Boolean;
  }
}

And now our refactored View type:

<!-- UserView.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  layout="absolute" xmlns:local="*">
  <local:User id="user" />
  <mx:VBox>
    <mx:HBox>
      <mx:Label text="First Name:" />
      <mx:TextInput id="userFirstNameTextInput"
        text="{user.firstName}"
        change="{user.firstName = userFirstNameTextInput.text}"
        editable="{user.editable}" />
    </mx:HBox>
    <mx:HBox>
      <mx:Label text="Last Name:" />
      <mx:TextInput id="userLastNameTextInput"
        text="{user.lastName}"
        change="{user.lastName = userLastNameTextInput.text}"
        editable="{user.editable}" />
    </mx:HBox>
    <mx:Button label="submit" click="user.submit()"
      enabled="{user.submitEnabled}" />
  </mx:VBox>
</mx:Application>

And finally, our refactored unit test:

public function testThatUserFirstAndLastNamesStartOutEmpty():void {
  var user:User = new User();
  assertEquals( user.firstName, "");
  assertEquals( user.lastName, "");
}

public function testThatUserFirstAndLastNamesCanBeSet():void {
  var user:User = new User();
  user.firstName = "Trygve";
  assertEquals( user.firstName, "Trygve");
  user.lastName = "Reenskaug"
  assertEquals( user.lastName, "Reenskaug");
}

public function testThatSubmitDisablesForm():void {
  var user:User = new User();
  user.firstName = "Trygve";
  user.lastName = "Reenskaug"
  assertTrue( user.submitEnabled );
  user.submit();
  assertFalse( user.submitEnabled );
  assertFalse( user.editable );
}

As long as we’re willing to rely on the MXML binding and inline ActionScript functionality, this is an effective (and quite scalable!) way to test program state and behavior.

For more information on Model View Controller, see

  • Patterns of Enterprise Application Architecture – Martin Fowler, 2002;
  • A Description of the Model-View-Controller User Interface Paradigm in the Smalltalk-80 System – Krasner & Pope, 1988.

For more information on unit testing in Flex, see FlexUnit – Adobe’s own Flex unit testing framework.

Karl Ridgeway

Karl currently works on new products for Rosetta Stone. Since joining the company in January 2006, he has been involved in the development of TOTALe, Rosetta Stone Classroom, and internal content creation tools.
blog comments powered by Disqus