Flex Unit Testing and Continuous Integration: Part 1 of 2

I wrote recently about a project where we had to put a Flex front end on top of a set of SOAP web services. Most server side programmers are hopefully now pretty comfortable with unit testing their server side code using something like JUnit. However, testing Flex applications isn't quite as mature yet and with a lot of Flex developers coming from backgrounds where unit testing isn't so ingrained, there isn't that much written about it.

There are three major different aspects of testing a Flex app that you might want to do.

  1. Unit testing: Testing individual bits of ActionScript such as individual classes or methods
  2. Integration testing: Testing how your app is actually wired together such as do the right events trigger the right commands
  3. Acceptance testing: Testing the whole app, including how it behaves in a realistic deployment environment

These are ordered in terms of simplicity. Unit testing as hopefully I will show is pretty simple, but things get a lot trickier when it comes to Integration and Acceptance testing, so I'll leave those for another article... hopefully by someone else!

Unit testing thankfully is also a good place to start because the more unit testing you do, the less integration and acceptance tests you are likely to have to write.

FlexUnit

FlexUnit is a testing framework that works in a very similar way to JUnit. The easiest way to explain how it works is to give some examples.

Let's take a simple ActionScript class: Airport.as

public class Airport
{
    [Bindable]
    public var name:String;

    public function Airport(name:String)
    {
        // line below deliberately broken by being commented out
        // this.name = name;
    }
}

Now, let's write a test to make sure that when you pass in a name to the Airport constructor, you do actually set the name.

FlexUnit comes in the form of a SWC that you include in your build, the most important class you get as part of that is TestCase. Whenever you write a test, you extend TestCase: AirportTest.as

public class AirportTest extends flexunit.framework.TestCase
{
    public function testAirportName() : void {
        var airportName:String = "My airport";
        var airport:Airport = new Airport(airportName);
        assertEquals("Airport name should have been set", airportName, airport.name);
    }
}

There are two naming conventions used here, one is the fact that I've called my test class [ClassI'mTesting]Test.as and that my actual test method (of which you can have as many in a single class as you like) is called testXXXXX(). Only methods with that name convention will be run. The file naming convention is just useful for organisation and finding tests more easily.

There are various methods available for testing in FlexUnit that all work in similar ways to assertEquals, such as assertTrue, assertFalse, assertNotNull, etc...

Now then, how do we run our test to see if it works? Hopefully it will fail first time and then we can fix it. This is quite a useful thing to do generally, it's called Test First/Driven Development. Ideally you'd define the behaviour of your class in your tests and then write the code that would allow those tests to pass.

Test Runner

FlexUnit provides a graphical test runner component that will allow you to see how your tests are getting on and view any errors and stack traces you might get from failing tests.

You use this by creating a new application in your project, mine is called TestRunner.mxml:

<!--?xml version="1.0" encoding="utf-8"?-->

    <![CDATA[
        import blackpepper.AllTests;
        import flexunit.framework.TestSuite;

        // Create the test suite and run the tests
        private function onCreationComplete():void
        {
              testRunner.test = AllTests.getAllTests();
              testRunner.startTest();
          }
    ]]>

<!-- FlexUnit GUI Component -->

The only bit of my code in there is the AllTests.getAllTests() snippet. That is there so that we can tell the test runner what tests we want it to run.

public class AllTests
{
    public static function getAllTests() : TestSuite {
        var testSuite:TestSuite = new TestSuite();
        testSuite.addTestSuite(AirportTest);
        testSuite.addTestSuite(AnotherTest);
        testSuite.addTestSuite(YetAnotherTest);
        return testSuite;
    }
}

You have to manually add your tests as you write them to this class. An alternative is to use Antennae to do some of that automatically for you.

Now, you just need to run the TestRunner.mxml application.

As you can see, our test failed as we expected, including a nice error telling us what went wrong. Now, if I uncomment that line in my Airport.as to make this test work, we get a nice green tick!

More advanced testing with Cairngorm and events

Now, that's about as simple a test as you can write. Part of the problem of testing Flex applications is that often your code is in an MXML file or your code is closely coupled to some other code that might be hard to test. Flex apps are also of course very event driven...how do you test that kind of behaviour?

In my application I have a login screen (LoginView.mxml). I'd like to be able to test the events that get fired when you try and login and after that, how my code works in response to an event. I'll not put in the MXML for the login screen, but the test should pretty clearly show what I expect it to do.

public class LoginViewTest extends TestCase
{
    private var userName:String = "User";
    private var password:String = "password";

    public function testLogin() : void {

        var loginView:LoginView = new LoginView();
        loginView.initialize();
        loginView.txtUserName.text = userName;
        loginView.txtPassword.text = password;

        CairngormEventDispatcher.getInstance().addEventListener(LoginEvent.LOG_IN_EVENT_TYPE, addAsync(listener, 2000));

        loginView.btnSubmit.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
    }

    private function listener(event:CairngormEvent) : void {
        var loginEvent:LoginEvent = event as LoginEvent;
        assertEquals(userName, loginEvent.userName);
        assertEquals(password, loginEvent.password);
    }

}

One of the tricks of testing MXML is that you need to call .initialize() on the component because otherwise it doesn't go through its normal lifecycle to setup the form and text input boxes I've got in there. My test then simulates a user entering their username and password into the two TextInputs. I then set up a Cairngorm event listener to listen for my expected LoginEvent that I hope to get when the user clicks the login button. I then simulate the clicking of that login.

Now, the big trick here is that you must think about the asynchronous nature of this test. Normally a unit test would just reach the end of the testLogin() method and end with a pass because we've not actually asserted anything. We need to tell FlexUnit to wait until my expected listeners have been called before considering the test as being over. You do this by wrapping the listener in the addAsync(listener, timeout) method. In my example the test will fail if listener is not called within 2 seconds.

Hopefully my listener is called and then my two asserts check that the login event contains the username and password I was expecting. Job done.

By splitting down the behaviour of your system into small chunks like this, you can write much more simple tests than if you tried to test the entire system in one go.

So, how do you then test the command that should be wired up to this LoginEvent that my login screen has fired? It works in a very similar way to the LoginView test, just manually create your LoginCommand and hand a manually crafted LoginEvent to its execute() method.

Now, the obvious problem here is that although you are nicely testing the functionality of your individual commands and views, you are kind of cheating in that you are manually wiring up event listeners. In reality when your application gets deployed, you might have a Cairngorm class that wires up all of your events and commands in one go, how do you test that? Not quite so easily unfortunately as that's really into the realm of integration and acceptance testing.

How to structure your project?

Different file structures will suit different people, but personally I have laid out my source and tests like this:

>src
->TestRunner.mxml
->Main.mxml
->blackpepper
-->model
--->Airport.as
>test-src
->blackpepper
-->model
--->AirportTest.as

Basically, I run a separate source folder for the tests and put them in the same package as the class that they are testing. Ideally I'd put TestRunner.mxml in the test-src directory as well, but there is a problem with FlexBuilder and multiple source directories that is stopping me :(

Running your tests as part of your build process with Ant

...this is going to have to wait for part 2...

This site uses cookies. Continue to use the site as normal if you are happy with this, or read more about cookies and how to manage them.

X