Flex & FlexUnit Patterns

May 20, 2010 - zenoconsultingzenoconsulting


Introduction

I've been writing a lot of Flex / ActionScript3 code lately. I've been very happy with the Mate framework for Flex. That said, one of the challenges with any UI development is testing. I have some other blog entries on the site that describe testing strategies for GWT, so I figured that I might as well throw some learnings about testing Flex on here as well.

When I googled around, I didn't really find a nice summary of these patterns, so I wanted to take a stab at it. There are a number of different testing frameworks for Flex out there. You can do your own homework on them, but I prefer FlexUnit. Incidentally, FlexUnit support is now included in the most recent release of FlashBuilder 4, so it does make it the obvious choice. You can also get Flex Unit support in maven with Flex Mojos.

Therefore, this page is focused on patterns with Flex Unit — and specifically some of these patterns make your code more testable in general which is a good thing.

Mate / MVC

Model-View-Controller is well documented, and there are a lot of variations, and offshoots like Model-View-Presenter, etc. I'm not too interested in an academic definition here. There are a couple of simple concepts to adhere to that make sense when designing UI-centric software:

  • UI code should be concerned only with UI issues (i.e. look pretty, present data cleanly, intuitive controls, etc.)
    • It shouldn't be fetching data from services
    • It shouldn't be formatting / transforming data, etc.
  • UI code also has to react to user input, but it doesn't have to handle the input there. Instead of writing code to handle user input, a nice way to deal with this is to just dispatch a custom event, and let someone else deal with it.

This is where the Mate framework comes in. Mate provides a nice event bus for Flex. When the UI needs to react to user input, it just dispatches an event. The Event Map is where you wire up the behavior (i.e. who handles the event). This could mean a service gets invoked. When the data is returned, we store it in a ModelManager class and use the data binding feature of Flex and the PropertyInjector feature of Mate to dynamically update the data in the View.

two_way_view_injection.png

I'm not going to go into great detail about this pattern. It is well defined at the Mate site, and there are several examples there to show you how to set all this up. In fact there is an awesome introductory video on Mate that you should watch if you aren't familiar with it:

The thing I do want to show is how to test UI components with FlexUnit.

Anti-Pattern: Calling Services in MXML

You'll see this pattern a lot when you read introductory material on Flex or read books on Flex. Unfortunately, following this approach makes your code untestable. In addition, because it mixes concerns where the View is responsible for calling services and updating itself, it makes it harder to maintain. If you separate this out (as per the Mate design approach above), we can test it properly.

Here's an example of the anti-pattern I'm talking about.

UIImpersonator Tests

Since you have to run your tests in FlashPlayer, you end up having the full framework at your disposal when you write the code for your tests. This makes it easy to simulate user interface events like mouse clicks, etc. There are a couple of things to keep in mind here though when you want to test a UI component, however. When I mention the term UI Component, I'm really talking about a custom MXML component that exists in your project.

Let's say we have a model object called Item.as that looks something like the following. This class is trivially easy to test itself with FlexUnit, so I'm not going to bother showing that.

package my.package {
 
   [Bindable]
   public class Item {
 
      private var _enabled:Boolean;
      private var _name:String;
 
      public function Item() {}
 
      public function get enabled():Boolean { return _enabled; }
      public function set enabled(val:Boolean):void { _enabled = val; }
 
      public function get name():String { return _name; }
      public function set name(val:String):void { _name = val; }
   }
}

Let's say we have a silly UI Component named ItemRenderer.mxml that you feed an Item object into. It shows a CheckBox to indicate if the item is enabled/disabled, and if the item's name is "FOO", then we show a custom image and a special tooltip message. I know this is a contrived example, but I'm trying to keep it simple.

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" height="19" width="100%" horizontalScrollPolicy="off">
    <mx:Script>
        <![CDATA[
             [Bindable] private var _item:Item;
 
             [Bindable] private var _disabledMessage:String;
 
             public function set data(item:Item):void {
                  _item = item;
                 if(_item.enabled == false && _item.name == "FOO") {
                       _disabledMessage = "I was disabled because my name is FOO";
                       disabledIcon.visible = true;
                 } else {
                      _disabledMessage = "";
                      disabledIcon.visible = false;
                 }
             }
        ]]>
    </mx:Script>
    <mx:Canvas 
      width="100%" 
      height="100%" 
      y="0" 
      right="0">
         <mx:ControlBar 
             height="100%" 
             width="100%" 
             verticalAlign="middle" 
             horizontalAlign="center"
             horizontalScrollPolicy="off" 
             paddingBottom="0" 
             paddingTop="0" 
             paddingLeft="0" 
             paddingRight="0">
            <mx:CheckBox 
                id="selectedBox" 
                enabled="false" 
                selected="{_item.enabled}"/>
            <mx:Image 
                y="1" 
                id="disabledIcon" 
                right="2" 
                toolTip="{_disabledMessage}"  
                source="@Embed(source=../assets/disabled.gif')"/>
       </mx:ControlBar>
    </mx:Canvas>
 
</mx:Canvas>

We can write a test for this UI component. The thing we have to bear in mind is that if you just "new" up an instance of ItemRenderer in your test, you'll end up with Null references because the object doesn't automatically create all of it's own UI component children immediately, but there is a very nice pattern here using the [UIImpersonator] to add the component to the mock stage, and wait for the FlexEvent.CREATION_COMPLETE to indicate that the object has finished construction and is ready to use. Here's how we can set this up in a FlexUnit test:

package my.package {
   import mx.events.FlexEvent;
   import org.flexunit.Assert;
   import org.flexunit.async.Async;
   import org.fluint.uiImpersonation.UIImpersonator;
 
   public class ItemRendererTest {
      // class under test
      private var _renderer:ItemRenderer;
 
      public function ItemRendererTest() {}
 
      [Before(async, ui)]
      public function setUp():void {
        _renderer = new ItemRenderer();
        Async.proceedOnEvent(this, _renderer, FlexEvent.CREATION_COMPLETE);
        UIImpersonator.addChild(_renderer);
      }
 
      [After(ui)]
      public function tearDown():void {
         UIImpersonator.removeChild(_renderer);
         _renderer = null;
      }
 
      [Test]
      public function testDisabled():void {
         var item:Item = new Item();
         item.enabled = false;
         item.name = "FOO";
         _renderer.data = item;
         var expected:String =   "I was disabled because my name is FOO";
         Assert.assertTrue(_renderer.disabledIcon.visible);
         Assert.assertEquals(expected, _renderer.disabledIcon.toolTip);
   }
}

Without the setup/teardown and the async part to wait for the FlexEvent.CREATION_COMPLETE we'd end up with a NULL reference when we tried to access the renderer's disabledIcon since the <mx:Image> would not have yet been created at the time the test method tries to access it.

This is a really simple, contrived example…but hopefully you can see how you'd be able to use this same pattern to test pretty much any UI component you write. In addition, if you're following the pattern identified above with Mate, where the UI only dispatches events, you can pretty much test 100% of the UI code — you don't have to even use mocks at all.

Creating The View With Custom Events

As I stated before, I like the idea of just having the View/UI respond to user input by dispatching an event. This makes the UI code testable, and not coupled to the implementation that is responsible for calling remote services and transforming data. Let's create a simple view class that takes an ISBN string number, and fires an event to have someone go fetch the book details from Amazon. It looks like this:

view.png

You type in the ISBN, hit Find, and it fills the data into the grid. The code for this is fairly simple…let's call it BookFinder.mxml:

<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009" 
         xmlns:s="library://ns.adobe.com/flex/spark" 
         xmlns:mx="library://ns.adobe.com/flex/mx" width="400" height="300">
 
    <fx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;
 
            private var _books:ArrayCollection;
 
            [Bindable]
            public function get books():ArrayCollection { return _books; }
 
            public function set books(val:ArrayCollection) { _books = val; }
 
            protected function onClick(event:MouseEvent):void
            {
                this.dispatchEvent(new AmazonIsbnLookupEvent(textInput.text));
            }
        ]]>
    </fx:Script>
 
    <s:Button x="185" y="6" label="Find" click="onClick(event)"/>
    <s:Label x="9" y="10" text="ISBN:"/>
    <s:TextInput id="textInput" x="51" y="7"/>
    <mx:DataGrid x="10" y="51" width="380" height="239" dataProvider="{books}">
        <mx:columns>
            <mx:DataGridColumn headerText="Title" dataField="col1" dataField="title"/>
            <mx:DataGridColumn headerText="Authors" dataField="col2" dataField="authors"/>
            <mx:DataGridColumn headerText="Date" dataField="col3" dataField="date"/>
        </mx:columns>
    </mx:DataGrid>
</s:Group>

We have a public property for an ArrayCollection of books which is the data provider for the grid. When the user clicks the button we dispatch this event:

public class AmazonIsbnLookupEvent extends Event
    {
        public static const ISBN_LOOKUP:String = "amazonIsbnLookup";
 
        private var _isbn:String;
 
        public function AmazonIsbnLookupEvent(type:String, isbn:String, bubbles:Boolean=true, cancelable:Boolean=false)
        {
            super(type, bubbles, cancelable);
            _isbn = isbn;
        }
 
        public function get isbn():String { return _isbn; }
    }

It is a trivial exercise to write a FlexUnit for the view using the UIImpersonator as shown above. We can easily test that if the button receives a click event, the event gets dispatched.

Next I'm going to show you how this all gets wired together.

Service Abstraction

One of the choices you'll need to make when developing Flex code that needs remote services is what type of service to use and what the representation format will be. Oftentimes you may have no control over this if you are consuming a 3rd party service that already defines these parameters. In general, though you have the following options:

  • Web Services with SOAP
  • REST with XML, JSON, or other representation format
  • AMF (binary)

I tend to prefer AMF for performance reasons. You may end up having to mix/match different types of service calls and different representation formats. I like to abstract this away, and stick to pure ActionScript objects as the data that gets delivered and bound to UI components. This makes it easy to test, and clean / easy to mix / match or change service implementations at will. Here's an example of what I mean:

Service%20Interface.png

Define the service interface, and then you can have several implementations of it. One of the great things about this is that you can implement a mock version of your service that might read the results from an XML file, and return them. This allows you to quickly develop your UI with mock data. You can build the entire UI and know exactly how it will look without requiring a back-end that provides the service endpoint. This allows parallel development — back-end developers and front-end developers can work independently of each other.

Ok, so the annoying thing about this is that you have to transform the representation format (e.g. XML) into ActionScript. This isn't all that tough to do, and I think it is worth it quite frankly because then all your UI code is coupled to your own objects. Think about it for a minute. If you tied your UI to XML, Flex has some great support for XML as a first-class object using e4x, and classes like XMLListCollection. Now consider what happens if your service provider offers a new JSON format and it performs much better than the identical XML service. The problem is that all of your UI code is coupled to XML, so you have to go tear it all up to use JSON if you want to switch.

If you abstract, you can mix and match. It also provides you an easy way to migrate to another service or do performance analysis against the different service endpoint simply by switching out the implementation with one line of code.

So, that's my rationale for tying my UI code to ActionScript model objects that I control. One of the projects I'm building uses Amazon Web Services. I use their RESTful ECommerce Service which delivers results in XML. So, basically all I had to do was use the REST API to get an XML result. I save that into the project's test resources, and now I can use that in a mock service implementation, and I can use it in Unit tests to check that my transformation of XML -> As3 works.

Here's an example of how to do this in FlexUnit. The class AmazonTransform here takes a raw AWS service result as XML (I looked up a book by ISBN number) and converts it into my own custom ActionScript representation of a Book.

package my.package {
 
    public class AmazonTransformTest {
 
        private static const AWS_XML:String = "resources/AWS_results.xml";
 
        // class under test
        private var transform:AmazonTransform;
 
        [Before]
        public function setUp():void {
            transform = new AmazonTransform();
        }
 
        [Test(async, timeout="1000")]
        public function testToBook_LoadXML():void {
            var urlLoader:URLLoader = new URLLoader();
            urlLoader.addEventListener(Event.COMPLETE, Async.asyncHandler(this, testToBook, 1000));
            urlLoader.load(new URLRequest(AWS_XML));
        }
 
        public function testToBook(event:Event, passThruData:Object):void {
            var xml:XML = new XML(event.target.data);
            var book:Book = transform.toBook(xml);
            Assert.assertEquals("Some Book Title", book.title);
            // etc..
        }
    }
}

So, here we have to use the async feature of FlexUnit since loading the XML file happens asynchronously. So now you have a simple class that can transform XML into your own object, let's define the service interface:

public interface IBookService {
 
    /**
      * Find a book given the ISBN number
      * @param isbn the ISBN 10 or 13
      */
    function find(isbn:String):AsyncToken;
}

That's simple enough, but what is this AsyncToken thing? Here's a good primer on it but in a nutshell, it allows you to correlate asynchronous results with a request. If this isn't 100% clear, just bear with me for a minute here. Let me show you the implementation of this interface.

public class AmazonService extends EventDispatcher implements IBookService {
     // the amazon AWSEcommere namespace
     public namespace awsns="http://webservices.amazon.com/AWSECommerceService/2009-01-06";
     use namespace awsns;
 
     public function lookup(isbn:String):AsyncToken {
    var response:AsyncToken=new AsyncToken(null);
    try {
        var httpService:HTTPService=new HTTPService();
        httpService.method="GET";
        httpService.resultFormat="e4x";
 
        httpService.url=AmazonUrlEncoder.encode(isbn, new Date());
        var resultHandler:Function=function(e:ResultEvent):void {
           for each (var responder:IResponder in response.responders) {
            var xml:XML=new XML(e.result);
            responder.result(new ResultEvent(ResultEvent.RESULT, false, true, xml));
           }
        };
        httpService.addEventListener(ResultEvent.RESULT, resultHandler);
        httpService.send();
        } catch (e:Error) {
             // TODO fault event here
             trace(e);
        } finally {
             return response;
        }
    }
}

So, AmazonUrlEncoder is my secret-sauce class that creates a URL for Amazon's web services. Not long ago they changed their API so you have to sign each request with an HMAC an timestamp it. The interesting part of this code here is where we attach the ResultEvent.RESULT to the AsyncToken. The respons is the raw XML we get back from Amazon. Someone else is responsible for converting that.

So, you might be wondering where the result goes. Let's define a simple ModelManager class for this that stores the result:

public class BookManager {
 
    private var _books:ArrayCollection = new ArrayCollection();
 
    private var _transform:AmazonTransform = new AmazonTransform();
 
    [Bindable]
    public function get books():ArrayCollection { return _books; }
 
    public function saveBooks(xml:XML):void {
        _books = _transform.toBookCollection(xml);
    }
}

That's simple enough. It has a single property that contains books. It has a method to save the books, where the XML from Amazon is passed in, and converted to an ArrayCollection of Book ActionScript objects.

Now, how is that all tied to the view? Here is where the magic of Mate comes in. This is the EventMap that wires it all together:

<mate:EventHandlers type="{AmazonIsbnLookupEvent.ISBN_LOOKUP}">
   <mate:DelegateInvoker generator="{AmazonService}" method="find" arguments="{[event.isbn]}" showBusyCursor="true">
      <mate:resultHandlers>
          <mate:MethodInvoker generator="{BookManager}" method="saveBooks" arguments="{[resultObject]}"/>
      </mate:resultHandlers>
   </mate:DelegateInvoker>
</mate:EventHandlers>
 
<mate:Injectors target="{BookFinder}">
   <mate:PropertyInjector targetKey="books" source="{BookManager}" sourceKey="books"/>
</mate:Injectors>

Remember the code above for the BookFinder.mxml — when the user clicked the Find button, it dispatched an AmazonIsbnLookupEvent? This is where it is caught, and then our service method is called. It is passed the isbn text from the event. When the result is returned, it is saved into the BookManager (which converts it from the raw XML into an ArrayCollection of Books. Finally, we use Mate's PropertyInjector to tie the results into the View class, so anytime this data is refreshed, the view will display the new results.

This is a great pattern, and I've used it extensively on a couple of projects. The other cool thing about this is that you can swap out the real service call with a class that mocks the data by loading it from an XML file — once again allowing you to develop and test your UI without the need for a real service on the back end.


Backlinks


Add a New Comment
or Sign in as Wikidot user
(will not be published)
- +
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License