Choosing to run tests based on the result(s) of another

The pre-release version of FlexUnit 4 supported this feature. Unfortunately, Flash Builder and several of its interfaces require a finite test count before tests are run. If you want to know how to do this in a quick and dirty sort of way, and you don't mind doing a little work, here is an approach you can refine.

In order to implement this workaround, you will have to sidestep Flash Builder's FlexUnit integration. Take a look at this support article on setting up FlexUnit for Flash Builder Standard, which allows you to get results from FlexUnit outside of Flash Builder.

First, create a static class or a singleton which implements IRunListener. Objects of this type can receive updates as tests run, fail, etc. So in your object you could keep track of any failure conditions which were important decision points.

public class SingletonIRunListener implements IRunListener 
{
...
}

In your FlexUnitApplication.mxml file, call the addListener() method of the FlexUnitCore and pass your object in. Add this right after the instantiation of the the core variable.

private function onCreationComplete():void
{
    core = new FlexUnitCore();
    core.addListener( SingletonIRunListener.getInstance() );
    core.addListener( new UIListener( testRunnerBase ) );
    core.run( currentRunTestSuite() ); 
}

This will register your object to receive the updates as mentioned above. Next step, create a new runner that extends Suite.

public class DecideSuiteRunner extends Suite 
{
...
}

In the new class, add a new function named ignoreRecursively(). This will cause each test in this suite to be marked as ignored in the output and will not execute it.:

protected function ignoreRecursively( notifier:IRunNotifier, description:IDescription ):void 
{
    if ( description.isTest ) {
        notifier.fireTestIgnored( description );
    } else {
        var children:Array = description.children;
        for ( var i:int=0; i < children.length; i++ ) {
            ignoreRecursively( notifier, children[ i ] );
        }
    }
}

The DecideSuiteRunner will also need an override to the protected runChild() method. This is the method where the suite will decide to run its children, based on some criteria.

override protected function runChild( child:*, notifier:IRunNotifier, childRunnerToken:AsyncTestToken ):void 
{
    var singletonIRunListener:SingletonIRunListener = SingletonIRunListener.getInstance();

    //The shouldIRunThisSuite( testClass ) function is where the 'run decision' logic exists

    if ( singletonIRunListener.shouldIRunThisSuite( testClass ) ) {
        super.runChild( child, notifier, childRunnerToken );
    } else {
        var description:IDescription = describeChild( child );

        ignoreRecursively( notifier, description );
        childRunnerToken.sendResult();
    }
}

Decorate your "optional" suite classes to run with the custom suite runner you just created.

[Suite(order=2)]
[RunWith("custom.runners.DecideSuiteRunner")]
public class OptionalSuite
{
    public var anotherCaseBasedOnDecision:AnotherCaseBasedOnDecision;
}

Finally, make sure you include a reference to the DecideSuiteRunner somewhere in your application. Metadata is just a string to FlashBuilder and it will not automatically know to include the PotentiallyIgnoreMeRunner in the final swf file. It is easiest just to include these references in the FlexUnitApplication.mxml file:

private var decideSuiteRunner:DecideSuiteRunner;

NOTE: In the above example, there is an order tag applied to the suite metadata. This is because a very simplistic logic has been applied with the SingletonIRunListener instance. In this case, there is a top level suite that runs a test case, based on the results of that test case it decides whether to run or ignore the next suite in the list. Order is used in order to guarantee that the results of the first test are intercepted by the SingletonIRunListener instance before the others are run. The IRunListener logic used is as follows:

private var passed:Boolean = true;

public function shouldIRunThisSuite(testClass:TestClass):Boolean {
    return passed;      
}

public function testFailure(failure:Failure):void
{
    if (failure)
        passed = false;
}

The testFailure() method will register any failures that come from the FlexUnitCore (all failures in this case). In this example, it sets the instance boolean passed to false if the last running test failed. This is an unnecessarily simple example, and a more specific implementation should be used in a full-force testing environment.

Here is what the TestRunnerBase UI displays when the first case fails. As you can see, the next two tests have been ignored as a result of the failure:

UIListener3Tests1Passed.PNG

Here is what the TestRunnerBase UI displays when the first case passes. As you can see, all three tests are run:

UIListener3Tests.PNG