Last posts:
Web Design » Web Development » CFCUnit + FlexUnit How-To: No excuses now!
CFCUnit + FlexUnit How-To: No excuses now!
Views: 1289 | 1-11-2010, 07:48 | Category : Web Development

This is a rather long post that walks through creating a ColdFusion service CFC and a Flex class to communicate with it using test-driven development tools.

Test-driven development: at first, it seemed like a lot of overhead. However, now that I've been doing it for a year and on multiple projects, I can't get enough. Believe it or not, you will get things done faster. Even better, I'm now more confident in my code, and I think it's made me an overall better developer.

However, I haven't been all that good about TDD on the Flex side of things. I'm stopping that now, and I'm going to use CFCUnit + FlexUnit to test everything from my lowest level DAO in ColdFusion to my client-side classes in AS3.

Here's how:

Note: this isn't an exhaustive resource on either CFCUnit or FlexUnit. It's more of a quick "how-to" to get you up and running with each, and to encourage you to explore the full potential and use of both tools.

Get the Tools

You can download CFCUnit from CFCUnit.org. I'm using the 1.1 RC1 release - the latest beta is breaking on my CF7 machine. Unzip, place /cfcunit under your web root. Place /org/cfcunit in whatever ColdFusion sees as "org.cfcunit" - if your CF root is the same as your Web root (95% of people's development boxes usually are!), that'd be something like c:\inetpub\wwwroot\org\cfcunit on Windows or /Library/WebServer/Documents/org/cfcunit on a stock OS X box.

FlexUnit is available at its GoogleCode Site. I'm using the only release available, and I placed it in (my web root)/com/adobe/flexunit, just because my web root is where I tend to keep all libraries I use.

Build a Test for a CFC

I'm going to write a service that returns the current time. Thrilling stuff.

Here's the weird part, though: I'm going to write my test first. This is largely what we mean by "test-driven development." The test is the coded definition of what your code must do. In this case, I'm going to require that I write a service with a function named "getDate" that returns a value that is a date and that the function is accessible remotely. It extends a base class in the CFCUnit framework. Any function whose name begins with test, has a public access, and returns void is considered a test function, and will be run by the CFCUnit test runner.

In a test function, you use what's called an assertion function to state that a condition must be true, false, greater than, etc:

<cfset assertTrue(1 eq 1, "Whoa, the universe is ending!") />


...would assert that one must be equal to one, and print the "Whoa!" message if "1 eq 1" evaluated to false.

Here's our test. In CF unit tests, I often test return types, access, etc., because we're not in a statically typed environment. Note that I test both that the function says it'll return a date and that it actually does.

Code for /com/firemoss/blogsamples/cfcandflexunit/TimeServiceTest.cfc:

<cfset md = getMetadata(svc.getDate) />
  
   <cfset assertTrue(structKeyExists(md, "returnType") and md.returntype eq "date", "Returntype not defined or not date!") />
   <cfset assertTrue(structKeyExists(md, "access") and md.access eq "remote", "Access not defined or not remote!") />
  
   <cfset date = svc.getDate() />
  
   <cfset assertTrue(isDate(date), "Returned value was not a date!") />
</cffunction>

</cfcomponent>


Allright. We know exactly what we need to go build. Let's create our service:

Code for /com/firemoss/blogsamples/cfcandflexunit/TimeService.cfc:

<cfcomponent>

<cffunction name="getDate" output="false" access="remote" returntype="date">
   <cfreturn now() />
</cffunction>

</cfcomponent>


Ok, more test code than implementation. It happens.

Now, let's fire up cfcunit at http://localhost/cfcunit. It'll ask us for our test class, and we give it the component name of the test, "com.firemoss.blogsamples.cfcandflexunit.TimeServiceTest". Then, we click run. If everything's good, we'll see an "All Passed!" message and a green checkbox on the TimeServiceTest row.

Try removing the "access='remote'" bit from our TimeService and re-running the test. You'll see that it breaks. Add it back, and we're ready to move on to the Flex side of things because we've got proof our CF service is working.

Build a local test in Flex

Allrighty, time to build out the Flex side. It's a bit more complicated.

First, we need to create two Flex projects - we want the classes we're testing separate from the app that tests them.

* TimeExampleLibrary - Create this as a Flex Library Project.
* TimeExampleTestRunner - Create this as a Flex Project.

Building a TestRunner UI

We need to set up TimeExampleTestRunner as our UI for running tests. Unlike CFCUnit, we can't just browse to a page: we need a Flex application that'll run our tests. It's possible to use Fling to create a generic test runner, but we'll stick to the simple-but-manual way.

Right-click the TimeExampleTestRunner project, and do Properties -> Flex Library Build Path -> Library Path -> Add SWC... and browse to / select the FlexUnit.swc library that's in the /bin directory of the FlexUnit distribution .zip.

Now we'll create a simple UI that defines the TestSuite (collection of test classes) we'd like to run as well as a UI to display their results. You might want to copy/paste this, because I change the namespace aliases to be more semantic.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:flexUnitUI="flexunit.flexui.*" xmlns:flexUnitFramework="flexunit.framework.*">

<flexUnitFramework:TestSuite id="testSuite" />

<flexUnitUI:TestRunnerBase id="testUI" test="{testSuite}" width="100%" height="100%" />

</mx:Application>


We can run it and see that we've got a UI but no tests. Let's change that.

Building an AS3 Test

What we need to do now is create the Flex equivalent of our TimeServiceTest CFC. I generally wrap up all interaction with backend services in a delegate class, so my test is going to specify that I need to implement a TimeServiceDelegate, and that I should be able to call it, receive a ResultEvent, and the value of ResultEvent.result should be a date.

Because we're working in an asynchronous environment, we can't just write a simple test: the test'll finish running before our ColdFusion server responds. What we need to do is use the addAsync() function of the FlexUnit TestCase class (which our test will extend) to "wrap" our result handler (I just throw faults from my fault handler - haven't found a super clean way to do this). Instead of adding our result function directly:

var responder:Responder = new Responder(resultFunction, faultFunction);


We ask addAsync() to get our result function, registering the async method with the test and setting a timeout of 5 seconds:

var responder:Responder = new Responder(addAsync(resultFunction, 5000), faultFunction);


Ok, here's the test:

package com.firemoss.blogsamples.cfcandflexunit.business
{
   import flexunit.framework.TestCase;
   import flexunit.framework.TestSuite;

   import mx.rpc.events.FaultEvent;
   import mx.rpc.events.ResultEvent;
   import mx.rpc.Responder;

   import com.firemoss.blogsamples.cfcandflexunit.business.TimeServiceDelegate;

   public class TestTimeServiceDelegate extends TestCase
   {
      
      public function TestTimeServiceDelegate(methodName:String=null) {
         super(methodName);
      }
      
      public static function suite():TestSuite {
         var suite:TestSuite = new TestSuite();
         suite.addTest(new TestTimeServiceDelegate("testColdFusionGetDate"));
        
         return suite;
      }
      
      public function testColdFusionGetDate():void {
         var responder:Responder = new Responder(addAsync(coldfusionGetDateResult, 5000), coldfusionGetDateFault);
         var del:TimeServiceDelegate = new TimeServiceDelegate(responder);
        
         del.getDate();
      }
      
      private function coldfusionGetDateResult(data:Object):void {
         assertTrue("Data should be ResultEvent", data is ResultEvent);

         var resultEvent:ResultEvent = data as ResultEvent;
         assertTrue("resultEvent.result should be a date", resultEvent.result is Date);
      }

      private function coldfusionGetDateFault(data:Object):void {
         throw(data as FaultEvent);
      }
   }
}


Ok, we've got a test. It won't compile, though - we need a TimeServiceDelegate class! Add the following folder to the TestExampleLibrary project: "/com/firemoss/blogsamples/cfcandflexunit/business/".

In it, add the following class file. Replace the "http://localhost/flex2gateway/" with the URL of your flex2gateway (it's probably the same as mine, or something like http://localhost:8500/flex2gateway/ if you're running ColdFusion with the standalone web server).

Code for TimeServiceDelegate:

package com.firemoss.blogsamples.cfcandflexunit.business
{
   import mx.messaging.ChannelSet;
   import mx.messaging.channels.AMFChannel;
   import mx.rpc.IResponder;
   import mx.rpc.AsyncToken;
   import mx.rpc.remoting.RemoteObject;
  
   public class TimeServiceDelegate
   {
      private var svc:RemoteObject;
      private var responder:IResponder;
      
      public function TimeServiceDelegate(responder:IResponder):void {
         var chan:AMFChannel = new AMFChannel("my-cfamf", "http://localhost/flex2gateway/");
         var chanSet:ChannelSet = new ChannelSet();
         chanSet.addChannel(chan);

         this.svc = new RemoteObject("ColdFusion");
         this.svc.channelSet = chanSet;
         this.svc.source = "com.firemoss.blogsamples.cfcandflexunit.Timeservice";
         this.responder = responder;
      }
      
      public function getDate():void {
         var token:AsyncToken = this.svc.getDate();
         token.addResponder(this.responder);  
      }
      
   }
}



If your compiler says "Nothing was specified to be included in the library," you need to right click TimeExampleLibrary's project and do Properties -> Flex Library Build Path -> Classes and click the checkbox beside TimeServiceDelegate.

Your compiler probably tells you that it can't find TimeServiceDelegate for the TimeExampleTests project. That's OK. Right click TimeExampleTest's project and do Properties -> Flex Build Path -> Library Path -> Add SWC... and choose the TimeExampleLibrary.swc file in the /bin directory of the TimeExampleLibrary project. Now you're all linked up and you should compile. Time for our last hoop.

We need to update our runner UI, telling it about our test and instructing it to run on creationComplete. Simple code:

Updated source of TimeExampleTests.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" xmlns:flexUnitUI="flexunit.flexui.*" xmlns:flexUnitFramework="flexunit.framework.*"
   creationComplete="creationComplete()"
>

<mx:Script>
<![CDATA[
import flexunit.framework.TestSuite;
import com.firemoss.blogsamples.cfcandflexunit.business.TestTimeServiceDelegate;


private function creationComplete():void {
   var testSuite:TestSuite = new TestSuite();
   testSuite.addTest(TestTimeServiceDelegate.suite());
  
   testUI.test = testSuite;
   testUI.startTest();
}  
]]>
</mx:Script>

<flexUnitUI:TestRunnerBase id="testUI" width="100%" height="100%" />

</mx:Application>



Give TimeExampleTests a run, and you should see the bar at the top turn green: if so, your test was successful! To see a failure, change the:

assertTrue("Data should be ResultEvent", data is ResultEvent);


...line of TestTimeServiceDelegate to:

assertTrue("Data as FaultEvent (fails!)", data is FaultEvent);


Ok, you're now rolling with CFCUnit and FlexUnit testing your CFCs and AS3 classes! We could confidently create a TimeExampleClient project that links to the TimeExampleLibrary and uses the TimeServiceDelegate, knowing that it works!

Going Further

There's a lot of places to go from here, all worth exploring:

* Write a generic test runner, using Fling to configure your test suites.
* Testing your MXML components: they're just AS3 classes!
* Testing Model-Glue:Flex controllers or Cairngorm Commands
* Using CFCUnit's remoting services to write a single UI to test both CF and Flex


Tags: Flex, Best Practices



Read also:
  • Passing parameter into functions – by value and reference.
  • Ant and Subversion: Building Model-Glue
  • More CF + .NET Craziness
  • CF Queries in .NET via Web Services: A Better Approach
  • (Parody) My New Web 2.0 App

  • Vote
    What do you need?

    Website design
    Website development
    PHP Tutorials
    Other


    Hire Desk
    »
    Website Design & Development, Php Tutorials
    This is Web developer Blog TheDesignMag
    Only high quality web developming service