Developmentastic

  1. FlexUnit Testing PureMVC Code

    I've had the great privilege of immersing myself almost completely in Flex 3 and PureMVC these past few weeks. I'm familiar with ActionScript 3 because of my work with JavaScript and a few months ago I read the exceptionally fun book Actionscript 3.0 Animation: Making Things Move! I can't recommend that book enough if you want to learn how to develop programatic animations for fun or profit. Actually, I think the material in that book would make for a great high school trigonometry course, but I digress.

    I think it was Fran Lukesh who found PureMVC while searching for an MVC solution. The deciding factor for us to use PureMVC over Cairngorm was its language independence. At andCulture we code both Flex and Flash apps so it's a distinct advantage to use the same framework in both environments to keep things consistent.

    By far the most impressive feature of PureMVC is the documentation. Probably not what you were expecting, but it's true. I've never before used a project—proprietary or open source—that had comprehensive documentation like the PureMVC Implementation Idioms and Best Practices document. It's the type of documentation you read to get started learning it's ins and outs then come back to re-read 6 months later and find you've learned even more the second pass.

    I recently picked up a copy of the Flex 3 Cookbook and it opened my eyes to what Flex is capable of. There are tons of great code samples throughout the book, but the one section that especially caught my attention was on FlexUnit. I feel like an idiot because I still haven't got into unit testing even though the entire Internet has long since jumped on that bandwagon and left me in the dust. Since I'm already learning a completely new language and MVC framework, I needed to take the plunge and make unit testing part of the learning process.

    The examples below assume you have PureMVC and FlexUnit all setup, configured, and have a basic understanding of both. PureMVC takes a while to get used to, but FlexUnit is pretty simple and straight forward.

    The FlexUnit TestCase class provides the addAsync() factory method used to attach event handlers to EventDispatchers in order to test asynchronous events. Instead of passing just a handler to addEventListener(), using addAsync() lets FlexUnit know how long to wait for the event to be dispatched before it's declared a failure.

    dispatcher.addEventListener(eventName, addAsync(callback, 1000));
    

    Pretty simple stuff. Definitely not rocket surgery by any means.

    It only took a minute writing my first few Proxy tests before I hit a wall. Proxies send Notifications; they don't dispatch events. How are my unit tests supposed to listen for named Notifications instead of events? A few Google searches didn't clear anything up, either. I don't think unit testing has quite reached “mainstream” status yet in the ActionScript world let alone strange people like me who want to unit test PureMVC apps.

    And so the PureMVC FlexUnit Testing project was born. The idea is this:

    1. Create a generic EventDispatcher.
    2. Attach a listener to the EventDispatcher using addAsync() and wait for an event to be fired with the same name as the Notification we're concerned with.
    3. Manually register an Observer on the Proxy that will be sending out the Notification.
    4. When the Proxy sends out the Notification and the Observer is called, dispatch an event on the EventDispatcher.

    Here's what that process looks like in code form.

    public function registerObserver(view:IView, proxy:IProxy, notificationName:String,
        callback:Function, timeout:int):void {
    
    // Create the hacky EventDispatcher used to throw PureMVCNotificationEvents.
    var hackyDispatcher:EventDispatcher = new EventDispatcher();
    
    // Listen for the notification event on the hacky dispather. The event type
    // will be equal to the notification name.
    hackyDispatcher.addEventListener(notificationName, addAsync(callback, timeout));
    
    // Handler listening for the proxy to dispatch the specified notification.
    var handler:Function = function(notification:INotification):void {
    
        // Notification received so dispatch a new PureMVCNotificationEvent giving
        // it the received INotification object.
        hackyDispatcher.dispatchEvent(new PureMVCNotificationEvent(notification));
    };
    
    // Manually register an observer on this proxy for the specified notification.
    view.registerObserver(notificationName, new Observer(handler, this)); }
    

    The hackDispatcher dispatches a custom event called PureMVCNotificationEvent (link to the source—it's super simple) passing it the observed Notification. PureMVCNotificationEvent's job is to set the event name to the name of the Notification and provide an accessor for event handlers to get to the Notification data.

    I created the PureMVCTestCase class which extends FlexUnit's TestCase and dumped registerObserver() there. When creating a new test case, simply extend from PureMVCTestCase.

    The concept is so simple, I'm sure that either someone already did this or I'm completely overlooking the blindingly obvious. Save money's on the latter.

    Here's what a very simple test method in a PureMVCTestCase looks like:

    private function get proxy():UserProxy {
        return ApplicationFacade.getInstance(PureMVCDemo.NAME)
            .retrieveProxy(UserProxy.NAME) as UserProxy;
    }
    
    private function get view():IView {
        return View.getInstance(PureMVCDemo.NAME);
    }
    
    public function testListUsers():void {
        // Call handleListUsersResponse when LIST_USERS_RESPONSE is sent.
        registerObserver(this.view, this.proxy, UserProxy.LIST_USERS_RESPONSE,
            handleListUsersResponse, 1000);
    
        // Call a proxy method that will eventually trigger LIST_USERS_RESPONSE.
        this.proxy.listUsers();
    }
    
    private function handleListUsersSuccess(e:PureMVCNotificationEvent):void {
        // An array of users is sent along with the LIST_USERS_RESPONSE Notification.
        // Pull it out and run some tests against it.
        var users:ArrayCollection = e.notification.getBody() as ArrayCollection;
    
        assertNotNull("Users returned are null", users);
        assertTrue("No users returned.", users.length > 0);
    }
    

    I created a very basic example project you can run to see how it works first hand.

    So check out the project, poke it, break it, and give some feedback. I'd love for this project to become obsolete either because PureMVC or FlexUnit integrate this logic into their projects or someone slaps me around and shows me something I'm overlooking.

    The PureMVC FlexUnit Testing project is open sourced with the permission of andCulture under the BSD License.

    Published on Wednesday, June 11 at 8:37am 8 comments so far.

    Comments

    1. James Knight said on Wednesday, June 11 at 8:15am Permalink

      I've done something similar, and was prompted to blog it after seeing your article: http://blog.hoardinghopes.com/index.php/2008/06/testing-proxies-in-puremvc/. Thinking about it, I like your implementation better because you're using an Observer, whereas I subclass Mediator to the same effect.

    2. Larry Marburger said on Wednesday, June 11 at 8:37am Permalink

      Great to meet another fellow PureMVC unit tester!

      Now I know I'm not completely insane as there's at least one other person out there who couldn't figure out a "correct" way to unit test PureMVC.

      I do like your implementation. In some aspects it makes more sense because with mine you need to pass some view component in order to use registerObserver which isn't very clean. I didn't do enough research to see if there is a better method for registering Observers without needing to use registerObserver() on an IView.

    3. Matt Schick said on Tuesday, June 17 at 2:53pm Permalink

      Hey,

      Awesome idea for testing proxys! But I'm trying to get your example to work and I'm running into a roadblock. I'm getting 2 errors I can't pin down:

      1067: Implicit coercion of a value of type com.mycompany.project.model:TaskProxy to an unrelated type org.puremvc.as3.multicore.interfaces:IProxy.  Project/src/test/com/mycompany/project/model    TaskProxyTest.as   Line 53
      
      1067: Implicit coercion of a value of type org.puremvc.as3.interfaces:IView to an unrelated type org.puremvc.as3.multicore.interfaces:IView.    Project/src/test/com/mycompany/project/model    TaskProxyTest.as  Line 53
      
      1119: Access of possibly undefined property notification through a reference with static type com.andculture.puremvcflexunittesting:PureMVCNotificationEvent.   Project/src/test/com/mycompany/project/model    TaskProxyTest.as    line 58
      

      And the lines the errors originate from:

      line 53:
      registerObserver(this.view, this.proxy, ApplicationFacade.LOAD_TASKS_SUCCESS, handleLoadTasksSuccess, 1000);
      
      line 58:
      var tasks:Array = e.notification.getBody() as Array;
      

      So I first double checked my TaskProxyTest.as file to make sure I was using the exact same syntax (and just swaping in my TaskProxy for your UserProxy where appropriate), and everything was the same.

      So then I grabbed your example project from http://puremvc-flexunit-testing.googlecode.com/svn/example and when compiling I get the same errors from your project just twice as many because you have two tests:

      1067: Implicit coercion of a value of type com.andculture.puremvcflexunittesting.example.model:UserProxy to an unrelated type org.puremvc.as3.multicore.interfaces:IProxy.  PureMVCFlexUnitTesting Example/src/com/andculture/puremvcflexunittesting/example/model  UserProxyTest.as    line 66
      
      1067: Implicit coercion of a value of type com.andculture.puremvcflexunittesting.example.model:UserProxy to an unrelated type org.puremvc.as3.multicore.interfaces:IProxy.  PureMVCFlexUnitTesting Example/src/com/andculture/puremvcflexunittesting/example/model  UserProxyTest.as    line 74
      
      1067: Implicit coercion of a value of type org.puremvc.as3.interfaces:IView to an unrelated type org.puremvc.as3.multicore.interfaces:IView.    PureMVCFlexUnitTesting Example/src/com/andculture/puremvcflexunittesting/example/model  UserProxyTest.as    line 66 
      
      1067: Implicit coercion of a value of type org.puremvc.as3.interfaces:IView to an unrelated type org.puremvc.as3.multicore.interfaces:IView.    PureMVCFlexUnitTesting Example/src/com/andculture/puremvcflexunittesting/example/model  UserProxyTest.as    line 74 
      
      1119: Access of possibly undefined property notification through a reference with static type com.andculture.puremvcflexunittesting:PureMVCNotificationEvent.   PureMVCFlexUnitTesting Example/src/com/andculture/puremvcflexunittesting/example/model  UserProxyTest.as    line 90
      
      1119: Access of possibly undefined property notification through a reference with static type com.andculture.puremvcflexunittesting:PureMVCNotificationEvent.   PureMVCFlexUnitTesting Example/src/com/andculture/puremvcflexunittesting/example/model  UserProxyTest.as    line 107
      

      and the respective lines:

      line 66:
      registerObserver(this.view, this.proxy, UserProxy.LIST_USERS_RESPONSE, handleListUsersResponse, 1000);
      
      line 74:
      registerObserver(this.view, this.proxy, UserProxy.LIST_USERS_RESPONSE, handleListUsersResponseFail, 1000);
      
      line 90:
      var users:Array = e.notification.getBody() as Array;
      
      line 107:
      var users:Array = e.notification.getBody() as Array;
      

      It seems to me that something is trying to utilize the puremvc multicore when it supposed to be using the puremvc standard. I don't mean to pass blame, but I don't think it's my code because I'm getting the same error when I run your example. However it could also be the Eclipse being crazy, but I have always used the puremvc standard and not multicore. Oddly I can't find a single reference to loading the multicore anywhere in your example or in my code.

      Any idea at all what is going on here?

      I'm using puremvc standard 2.0.3, eclipse 3.3.2, and flex 3

    4. Matt Schick said on Tuesday, June 17 at 2:54pm Permalink

      note to self: put more blanklines between paragraphs in large posts

    5. Larry Marburger said on Tuesday, June 17 at 3:42pm Permalink

      That's odd. When I first created the project, I was using multicore because I ripped it out of another multicore project I was working on. I switched it to standard almost immediately, though.

      Check the /libs directory in your PureMVCFlexUnitTesting and Example projects. Did a multicore SWC sneak its way in there? I did a global search just as you did and also didn't find any residual references to multicore anywhere in the code.

      I'll do a fresh checkout of both tonight and do a dry run to see if I can replicate your issue.

      For the record, I'm running Flex Builder 3.0.194161 on OS X 10.5.3.

      I was poking around on the PureMVC forums and Cliff made a good point about unit testing PureMVC. If you use multicore, you can create a new core for each test so you know it's isolated. With standard, you have to take extra care to make sure your singletons are cleaned up after each test. I haven't done anything with this yet, but it sounds like the route this project should be headed.

    6. Matt Schick said on Wednesday, June 18 at 5:00pm Permalink

      I'm pretty sure I figured out the root of those errors.

      In Eclipse I typed out an import statement at the top of my test case: import org.puremvc.as3.multicore.interfaces.IProxy;

      and then selected 'IProxy' and hit F3 (open declaration - opens up the source file where that declaration is found) and it popped up an error saying that it couldn't locate the source file inside of the PureMVCFlexUnitTesting_1.0.swc, meaning that the multicore code existed inside of the swc. So I grabbed the PureMVCFlexUnitTesting.swc file from the svn trunk and everything was peachy. No errors no nothing. The swc that was giving me trouble was from the downloads page in google code: http://code.google.com/p/puremvc-flexunit-testing/downloads/list

      In regards to using multicore for future testing: It sounds like a good idea to me. I haven't even looked at multicore yet so all I know is that it has multiple cores : ) I'm new to flex, puremvc, and flexunit, so switching to yet another variation of puremvc makes me cringe right now. Maybe in the future...

    7. Larry Marburger said on Thursday, June 19 at 7:46am Permalink

      Oh man, I forgot all about the compiled SWC I stuck up there. That was definitely built before I switched to the standard version. Thanks for catching that!

      I'm new to the whole Flex / PureMVC / FlexUnit scene as well. MultiCore is useful if you're going to build an app that will load modules and you want to ensure that multiple instances of the same module can be on the stage at once without them tripping over each other. For example, when one module instance sends a notification, you don't want all other instances of that module to respond.

      If that's the case, you should definitely check out MuitiCore. It's practically transparent. All the changes are hidden below the surface.

      Another fun PureMVC project / framework is Pipes. I've read about it, but haven't used it yet. It facilitates a standard method for an application to communicate with its modules and vice versa.

    8. Kieran said on Friday, October 1 at 7:32am Permalink

      Hi Larry,

      Thanks for coming up with this code, I just started trying out PureMVC and was disappointed to find that no one really seems to have thought about testing :(

      I just downloaded your code from http://code.google.com/p/puremvc-flexunit-testing/ and will try it out. It would be useful if you linked to this blog entry from your google code project as there isn't much info there about the code.

      Cheers, Kieran

    Hooray for spam! I thought I was clever implementing some hot bot-slaying JavaScript, but turns out they weren't as stupid as I originally thought. Well done, bots.

    I'm disabling commenting yet again until I devise my next strategy.