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.

  • Matt

    Thats intense!

    Good work to you and the rest of the team! I love this program! : )

  • http://blog.75thtrombone.com/ Lanny Heidbreder

    I came to the Rosetta Stone site just now to see if I could find anything about a possible future version of RS for the iPad. That would be incredibly awesome. The software as it is would be great with a touch interface, and with the iPad you could properly and awesomely teach people to WRITE languages with completely different alphabets. Learning to write in Japanese by drawing the characters with my finger would be amazing.

    So I came here to look for iPad stuff and found this entry, which says that all Rosetta Stone products are written in Flash. Now I’m leaving the site, a little sadder than when I came.

    • rvoiceadmin

      @Lanny: We will be releasing an iPhone/mobile app later this year
      http://bit.ly/bl5Kio. And while we’ve made no official announcement, we are excited about the possibilities for Rosetta Stone programs on the iPad!

  • N.K. Primorje

    Which tool did you use for developing Rosetta Stone for iPad? They do not support Flesh runtime.

  • N.K. Primorje

    I did meant to provoke you with question what technology did you use on iPad. It is just that I need to write an application with high level of interactivity for one of my study classes and I really really like your approach. The condition is that it needs to run on iPad. I was just hoping that you will share some thoughts on which technology did you use and why did you use it. If it is a company secret than of course I understand.

    • Rosetta Stone

      Hi NK,
      TOTALe Companion HD was written in Objective-C with some help from a couple of C libraries. So it’s a completely native iPad application, but it runs using the same language data as the regular TOTALe application. Good luck with your project!
      -Wiley Kestner

  • N.K. Primorje

    *I of course meant to say that I did NOT meant to provoke you. Sorry for typo.

  • N.K. Primorje

    Thank you very very much for your help.

  • http://%/ghjffgfa5998 Alfonso

    .

    tnx for info.

  • http://%/fbajjfdh18 donald

    .

    tnx for info.

  • http://%/ejbicbjaj48 Vincent

    .

    thanks for information.

  • http://wthenrie.tumblr.com/ Steam Wallet Hack

    Steam Wallet Hack

    How to Design for Unit Testing in Flex | Rosetta Stone® Blog

  • http://leah-is-justins-whoran.tumblr.com/ steam wallet hack tool

    steam wallet hack tool

    How to Design for Unit Testing in Flex | Rosetta Stone® Blog

  • https://www.facebook.com/download.blog.net/ media

    media

    How to Design for Unit Testing in Flex | Rosetta Stone® Blog

  • https://www.instagram.com/downloadblognetsite/ download-blog-net

    download-blog-net

    How to Design for Unit Testing in Flex | Rosetta Stone® Blog

blog comments powered by Disqus