Home  >  

Anatomy of an Enterprise Flex RIA Part 17: Testing Flex

Author photo
AddThis Social Bookmark Button

In the last installment we saw how we use Cairngorm's model locater to configure the view using Flex's binding. Now we're going to have a look at testing flex with Flex unit and automating those tests.

We’ve seen how we use TestNG to unit-test the application’s persistence layer. Flex has a unit-testing framework too, called FlexUnit.

Testing visual applications is never as easy as testing non-visual code. The Flex framework has some automation classes for use with automated testing products such as Mercury TestDirector, which can simulate user interaction with an application. Here, though, we’ll just make sure a few things are happening the way we expect, mostly as an example of how to automate FlexUnit tests using Ant.

Figure 21 shows the files we’ll be looking at.

series17_figure21.jpg
Figure 21. Flex Unit Files

The Flex application, bookie_tests.mxml, defines the test suites to run and provides a .swf to run the tests. test/BookieTests.as contains one test suite.

Here’s the test runner code from bookie_tests.mxml:

<mx:Application
    ...
    creationComplete="test()"
    >
    
<mx:Script>

private var runner:JUnitTestRunner;

private function test():void {
     status.text = "Testing...";
     runner = new JUnitTestRunner();
     runner.run(suite(), onTestComplete);
}

private function suite():TestSuite {
     var testSuite:TestSuite = new TestSuite();
 
     testSuite.addTest(BookieTests.suite());
 
     return testSuite;
}

private function onTestComplete():void {
     status.text = "Test complete, safe to close.";
     fscommand("quit");
}
...
<control:BookieController id="controller" />
<mx:Text id="status" />
...

Once the application finishes loading, it calls test(). This method creates a new JUnitTestRunner and adds the TestSuite returned by the suite() method. That method returns the test suite from BookieTests. Anyone familiar with JUnit, a Java unit-testing framework, will recognize this process.

Notice that we have a text control to display a status message. Above that is a BookieController. Including the controller here allows us to test the interactions of the Cairngorm commands with parts of the application, most likely the model, as we’ll see in a second.

This process is slightly different from the standard way of running FlexUnit tests. The JUnitTestRunner is also slightly different from the standard FlexUnit test runner. The standard test runner displays the test results or failures in a Flex interface, but in our case, we’re more interested in stopping the automated build if the tests fail.

The JUnitTestRunner depends on being run as part of an Ant build. When the Ant build kicks off a Flex unit task, it opens a server and waits for the Flex test application to connect. It then runs the test and decides whether any tests failed. If any tests fail, the Flex unit Ant task can halt the build with an error. It doesn’t show any results in the browser, though. One possible way around that limitation is to run a standard FlexUnit runner from a different application that runs the same test suites, and use that to get information about failing tests.

Here is the section from the build.xml Ant build file that runs the Flex tests:

<target name="compile-flex-tests" description="compiles bookie_tests.mxml">
    <echo message="Compiling bookie_tests.mxml" />
    <exec executable="${flex.sdk.bin.dir}/mxmlc" dir="${src.dir}" >
         <arg value="-load-config"/>
         <arg value="${flex.config}" />
         <arg value="-library-path+=${src.dir}/lib"/>
         <arg value="-services"/>
         <arg value="${services.config}" />
         <arg value="bookie_tests.mxml" />
    </exec>
    <move file="${src.dir}/bookie_tests.swf" todir="${build.dir}" verbose="true" overwrite="true" />
</target>

<target name="test-flex" depends="compile-flex-tests">
    <flexunit
         timeout="0"
         swf="${build.dir}/bookie_tests.swf"
         toDir="${build.dir}"
         haltonfailure="true" />

</target>

Calling 'ant test-flex' first compiles the test application to the build directory; t. Then it calls the flexunit custom task that we defined earlier in the file and looked at in the discussion on the build file. This task points to the compiled test .swf and knows how to open that .swf on the local system. We also specify that we want the build to fail if the Flex tests fail. Try it by calling 'ant test-flex' from the bookie-ui root directory or 'mvn test' from either the bookie-ui directory or the project root directory. The only requirement as part of an automated build is that there is a browser is available on the system that can run a .swf.

Let’s look at the tests in tests.BookieTests:

public static function suite():TestSuite {
     var testSuite:TestSuite = new TestSuite();
 
     testSuite.addTest(new BookieTests("testModelsAreSingletons"));
     testSuite.addTest(new BookieTests("testSignOutCommand"));
 
     return testSuite;
}

The suite method returns a TestSuite with all the tests added and ready to run. The test application gets the TestSuite from this method. The two tests we have are testModelsAreSingletons and testSignOutCommand, shown here:

public function testModelsAreSingletons():void {
     var caughtBookieModelInstantiationError:Boolean;
     var caughtAdminModelInstantiationError:Boolean;
     var model:BookieModel;
     var adminModel:AdminModel;
 
     try {
           model = new BookieModel(null);
      } catch (e:Error) {
           caughtBookieModelInstantiationError = true;
      }
 
     try {
           adminModel = new AdminModel(null);
      } catch (e:Error) {
           caughtAdminModelInstantiationError = true;
      }
 
     assertTrue("Expected error instantiating singleton BookieModel", caughtBookieModelInstantiationError);
     assertTrue("Expected error instantiating singleton AdminModel", caughtAdminModelInstantiationError);
     assertUndefined(model)
     assertUndefined(adminModel);
 
 
     model = BookieModel.getInstance();
     adminModel = AdminModel.getInstance();
 
     assertNotUndefined(model)
     assertNotUndefined(adminModel)
 
}

This test makes sure an error is thrown if any code tries to instantiate a model in any way except through calling getInstance. This enforces the singleton pattern of the ModelLocators.

public function testSignOutCommand():void {
     assertEquals(
          "Model should initialize state to SIGNED_OUT",
          model.currentState,
          BookieModel.SIGNED_OUT
     )
 
     // set state as if we were signed in
     model.currentState = BookieModel.SIGNED_IN;
 
     new SignOutEvent().dispatch();
     assertEquals(
          "Model should be SIGNED_OUT after sign out event.",
          model.currentState,
          BookieModel.SIGNED_OUT
     )
}

This test makes sure that the SignOutCommand sets the state on the model back to the SIGNED_OUT constant after it’s done. I chose this command to show that you can test Cairngorm interactions, but it doesn’t do any service interaction. Service interaction would require tests to be run from inside JBoss, or for us to implement a Mock Object for the services. Either of these is doable. The tests we have here are a good start, and they show how to run the Flex tests using Ant.

Services, delegates, controllers, commands, and models are the parts that help to perform the non-visual tasks that make a data-driven application tick. Tests help us to make sure that things we expect to happen are happening. Next, we’ll look at the part the user can see: the view.

The next installment is going to bring us closer to building the Flex UI of our application. You can always find the entire series here.

Read more from Tony Hillerson. Tony Hillerson's Atom feed thillerson on Twitter

Comments

5 Comments

Erhan Kayar said:

Hi Tony,
Thank you for this great series. When you finish this series, could you make a pdf that includes all chapters. Looking for another chapter.

Maxim Porges said:

Not to be a stickler, but I think the app that is used for testing the front end of Flex apps is QuickTest Pro (formerly WinRunner). It was rebranded after HP bought Mercury.

We had the ex-Mercury HP testing suite at my last employer. The QuickTest Pro functionality works great for Flex, but the LoadRunner support for AS 3 was barely operational. They are working on it, but we were disappointed with the fact that the LoadRunner stuff didn't work.

- max

@Maxim thanks for the correction.

@Erhan - you can buy the shortcut as a pdf at this location: http://www.oreilly.com/catalog/9780596514402/?CMP=ILC-dm_nav_related-books

Jim Cox said:

I also use this testing strategy to make sure that my Cairngorm events are wired up correctly.

If your ServiceLocator uses HTTPServices, you can extend this strategy for tests that touch the back end without running them in the container or having to implement Mock Objects.

In my test harness, I point to a different service locator implementation. eg:

<business:TestServices id="services" />

In TestServices, I define all the same services - but the url resolves to a file. eg:

<mx:HTTPService id="myService" resultFormat="xml" url="xml/myTestResults.xml" />

One little wrinkle - since the data is loaded asynchronously, your test needs to wait for the data to be loaded before you can make your assertions. eg

service.addEventListener(ResultEvent.RESULT, addAsync(verifyMethod, 3000));

codecraig said:

Do you have any information on using Mock objects with FlexUnit or ASUnit?

I am using PureMVC but the same problem exists, how do I test my class that accesses a webservice? I could extend the class for testing and have it fake the results but having a mock framework handle that would be nice.

Leave a comment


Tag Cloud

Question of the Week: Open Source Flex Projects

What would you say are the 5 most prominent open source projects in the Flex world?

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.